@fortify-ts/core
Advanced tools
+138
| # @fortify-ts/core | ||
| Core types, errors, and utilities for the Fortify-TS resilience library. | ||
| ## Installation | ||
| ```bash | ||
| npm install @fortify-ts/core | ||
| # or | ||
| pnpm add @fortify-ts/core | ||
| ``` | ||
| ## Features | ||
| - **Type Definitions**: `Operation<T>`, `Pattern<T>`, `Closeable`, `Resettable` interfaces | ||
| - **Error Hierarchy**: `FortifyError` and pattern-specific error classes | ||
| - **Utilities**: Signal combining, timeout helpers, jitter calculation | ||
| - **Validation**: Zod schemas for configuration validation | ||
| - **Storage**: In-memory storage with LRU eviction for rate limiting | ||
| ## Usage | ||
| ### Error Types | ||
| ```typescript | ||
| import { | ||
| FortifyError, | ||
| CircuitOpenError, | ||
| RateLimitExceededError, | ||
| BulkheadFullError, | ||
| TimeoutError, | ||
| MaxAttemptsReachedError, | ||
| } from '@fortify-ts/core'; | ||
| try { | ||
| await pattern.execute(operation); | ||
| } catch (error) { | ||
| if (error instanceof CircuitOpenError) { | ||
| // Handle circuit open | ||
| } else if (error instanceof RateLimitExceededError) { | ||
| // Handle rate limit | ||
| } | ||
| } | ||
| ``` | ||
| ### Retryable Errors | ||
| ```typescript | ||
| import { asRetryable, asNonRetryable, isRetryableError } from '@fortify-ts/core'; | ||
| // Mark an error as retryable | ||
| throw asRetryable(new Error('Temporary failure')); | ||
| // Mark an error as non-retryable | ||
| throw asNonRetryable(new Error('Permanent failure')); | ||
| // Check if an error is retryable | ||
| if (isRetryableError(error)) { | ||
| // Retry the operation | ||
| } | ||
| ``` | ||
| ### Utilities | ||
| ```typescript | ||
| import { | ||
| sleep, | ||
| withTimeout, | ||
| combineSignals, | ||
| throwIfAborted, | ||
| NEVER_ABORTED_SIGNAL, | ||
| } from '@fortify-ts/core'; | ||
| // Sleep with cancellation support | ||
| await sleep(1000, signal); | ||
| // Wrap a promise with timeout | ||
| const result = await withTimeout(fetchData(), 5000); | ||
| // Combine multiple abort signals | ||
| const combined = combineSignals(signal1, signal2); | ||
| // Check if signal is aborted | ||
| throwIfAborted(signal); | ||
| ``` | ||
| ### Configuration Schemas | ||
| ```typescript | ||
| import { | ||
| retryConfigSchema, | ||
| circuitBreakerConfigSchema, | ||
| rateLimitConfigSchema, | ||
| bulkheadConfigSchema, | ||
| } from '@fortify-ts/core'; | ||
| // Validate and parse configuration | ||
| const config = retryConfigSchema.parse({ | ||
| maxAttempts: 5, | ||
| initialDelay: 100, | ||
| }); | ||
| ``` | ||
| ## API Reference | ||
| ### Types | ||
| - `Operation<T>` - Async function that accepts AbortSignal: `(signal: AbortSignal) => Promise<T>` | ||
| - `Pattern<T>` - Interface for resilience patterns with `execute()` method | ||
| - `Closeable` - Interface for patterns that need cleanup | ||
| - `Resettable` - Interface for patterns that can reset state | ||
| ### Errors | ||
| | Error | Description | | ||
| |-------|-------------| | ||
| | `FortifyError` | Base class for all Fortify errors | | ||
| | `CircuitOpenError` | Circuit breaker is open | | ||
| | `RateLimitExceededError` | Rate limit exceeded | | ||
| | `BulkheadFullError` | Bulkhead at capacity | | ||
| | `BulkheadClosedError` | Bulkhead has been closed | | ||
| | `TimeoutError` | Operation timed out | | ||
| | `MaxAttemptsReachedError` | All retry attempts exhausted | | ||
| ### Utilities | ||
| | Function | Description | | ||
| |----------|-------------| | ||
| | `sleep(ms, signal?)` | Async sleep with cancellation | | ||
| | `withTimeout(promise, ms, signal?)` | Add timeout to promise | | ||
| | `executeWithTimeout(operation, ms, signal?)` | Execute operation with timeout | | ||
| | `combineSignals(...signals)` | Combine multiple AbortSignals | | ||
| | `throwIfAborted(signal)` | Throw if signal is aborted | | ||
| | `isAbortError(error)` | Check if error is AbortError | | ||
| ## License | ||
| MIT |
+85
-25
@@ -5,6 +5,2 @@ 'use strict'; | ||
| var __defProp = Object.defineProperty; | ||
| var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; | ||
| var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); | ||
| // src/errors.ts | ||
@@ -19,2 +15,12 @@ var FortifyError = class extends Error { | ||
| } | ||
| /** | ||
| * Serialize to JSON for structured logging. | ||
| * @returns JSON-safe representation of the error | ||
| */ | ||
| toJSON() { | ||
| return { | ||
| name: this.name, | ||
| message: this.message | ||
| }; | ||
| } | ||
| }; | ||
@@ -28,14 +34,30 @@ var CircuitOpenError = class extends FortifyError { | ||
| var RateLimitExceededError = class extends FortifyError { | ||
| key; | ||
| constructor(message = "Rate limit exceeded", key) { | ||
| super(message); | ||
| __publicField(this, "key"); | ||
| this.name = "RateLimitExceededError"; | ||
| this.key = key; | ||
| } | ||
| /** | ||
| * Serialize to JSON, optionally including the key. | ||
| * | ||
| * @param includeKey - Whether to include the rate limit key (default: false for security) | ||
| * @returns JSON-safe representation of the error | ||
| */ | ||
| toJSON(includeKey = false) { | ||
| const result = { | ||
| name: this.name, | ||
| message: this.message | ||
| }; | ||
| if (includeKey && this.key !== void 0) { | ||
| result.key = this.key; | ||
| } | ||
| return result; | ||
| } | ||
| }; | ||
| var BulkheadFullError = class extends FortifyError { | ||
| activeCount; | ||
| queuedCount; | ||
| constructor(message = "Bulkhead is full", activeCount = 0, queuedCount = 0) { | ||
| super(message); | ||
| __publicField(this, "activeCount"); | ||
| __publicField(this, "queuedCount"); | ||
| this.name = "BulkheadFullError"; | ||
@@ -45,16 +67,35 @@ this.activeCount = activeCount; | ||
| } | ||
| toJSON() { | ||
| return { | ||
| ...super.toJSON(), | ||
| activeCount: this.activeCount, | ||
| queuedCount: this.queuedCount | ||
| }; | ||
| } | ||
| }; | ||
| var TimeoutError = class extends FortifyError { | ||
| timeoutMs; | ||
| constructor(message = "Operation timed out", timeoutMs = 0) { | ||
| super(message); | ||
| __publicField(this, "timeoutMs"); | ||
| this.name = "TimeoutError"; | ||
| this.timeoutMs = timeoutMs; | ||
| } | ||
| /** | ||
| * Alias for timeoutMs for convenience. | ||
| */ | ||
| get duration() { | ||
| return this.timeoutMs; | ||
| } | ||
| toJSON() { | ||
| return { | ||
| ...super.toJSON(), | ||
| timeoutMs: this.timeoutMs | ||
| }; | ||
| } | ||
| }; | ||
| var MaxAttemptsReachedError = class extends FortifyError { | ||
| attempts; | ||
| lastError; | ||
| constructor(message = "Maximum retry attempts reached", attempts = 0, lastError) { | ||
| super(message); | ||
| __publicField(this, "attempts"); | ||
| __publicField(this, "lastError"); | ||
| this.name = "MaxAttemptsReachedError"; | ||
@@ -64,2 +105,9 @@ this.attempts = attempts; | ||
| } | ||
| toJSON() { | ||
| return { | ||
| ...super.toJSON(), | ||
| attempts: this.attempts, | ||
| lastError: this.lastError ? { name: this.lastError.name, message: this.lastError.message } : void 0 | ||
| }; | ||
| } | ||
| }; | ||
@@ -76,6 +124,6 @@ var BulkheadClosedError = class extends FortifyError { | ||
| var RetryableErrorWrapper = class extends Error { | ||
| retryable; | ||
| cause; | ||
| constructor(error, retryable) { | ||
| super(error.message, { cause: error }); | ||
| __publicField(this, "retryable"); | ||
| __publicField(this, "cause"); | ||
| this.name = "RetryableErrorWrapper"; | ||
@@ -141,2 +189,3 @@ this.retryable = retryable; | ||
| // src/utils.ts | ||
| var NEVER_ABORTED_SIGNAL = new AbortController().signal; | ||
| function sleep(ms, signal) { | ||
@@ -219,3 +268,3 @@ return new Promise((resolve, reject) => { | ||
| if (validSignals.length === 0) { | ||
| return new AbortController().signal; | ||
| return NEVER_ABORTED_SIGNAL; | ||
| } | ||
@@ -272,5 +321,16 @@ const firstSignal = validSignals[0]; | ||
| } | ||
| function addJitter(delay) { | ||
| const jitterFactor = 1 + Math.random() * 0.1; | ||
| return Math.floor(delay * jitterFactor); | ||
| function addJitter(delay, mode = "equal", previousDelay) { | ||
| switch (mode) { | ||
| case "full": | ||
| return Math.floor(Math.random() * delay); | ||
| case "decorrelated": { | ||
| const prev = previousDelay ?? delay; | ||
| const min = delay; | ||
| const max = Math.min(prev * 3, delay * 10); | ||
| return Math.floor(min + Math.random() * (max - min)); | ||
| } | ||
| case "equal": | ||
| default: | ||
| return Math.floor(delay * 0.5 + Math.random() * delay * 0.5); | ||
| } | ||
| } | ||
@@ -401,3 +461,10 @@ function clamp(value, min, max) { | ||
| } | ||
| var _MemoryStorage = class _MemoryStorage { | ||
| var MemoryStorage = class _MemoryStorage { | ||
| /** Cached resolved promise for void returns to avoid allocation */ | ||
| static RESOLVED_VOID = Promise.resolve(); | ||
| /** Cached resolved promise for null returns to avoid allocation */ | ||
| static RESOLVED_NULL = Promise.resolve(null); | ||
| entries = /* @__PURE__ */ new Map(); | ||
| maxEntries; | ||
| evictionCount = 0; | ||
| /** | ||
@@ -410,5 +477,2 @@ * Create a new in-memory storage. | ||
| constructor(options) { | ||
| __publicField(this, "entries", /* @__PURE__ */ new Map()); | ||
| __publicField(this, "maxEntries"); | ||
| __publicField(this, "evictionCount", 0); | ||
| this.maxEntries = options?.maxEntries ?? 1e4; | ||
@@ -496,7 +560,2 @@ } | ||
| }; | ||
| /** Cached resolved promise for void returns to avoid allocation */ | ||
| __publicField(_MemoryStorage, "RESOLVED_VOID", Promise.resolve()); | ||
| /** Cached resolved promise for null returns to avoid allocation */ | ||
| __publicField(_MemoryStorage, "RESOLVED_NULL", Promise.resolve(null)); | ||
| var MemoryStorage = _MemoryStorage; | ||
@@ -509,2 +568,3 @@ exports.BulkheadClosedError = BulkheadClosedError; | ||
| exports.MemoryStorage = MemoryStorage; | ||
| exports.NEVER_ABORTED_SIGNAL = NEVER_ABORTED_SIGNAL; | ||
| exports.RateLimitExceededError = RateLimitExceededError; | ||
@@ -511,0 +571,0 @@ exports.RetryableErrorWrapper = RetryableErrorWrapper; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/errors.ts","../src/types.ts","../src/utils.ts","../src/schemas.ts","../src/storage.ts"],"names":["z"],"mappings":";;;;;;;;;AAGO,IAAM,YAAA,GAAN,cAA2B,KAAA,CAAM;AAAA,EACtC,YAAY,OAAA,EAAiB;AAC3B,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,cAAA;AAGZ,IAAA,IAAI,OAAO,KAAA,CAAM,iBAAA,KAAsB,UAAA,EAAY;AACjD,MAAA,KAAA,CAAM,iBAAA,CAAkB,IAAA,EAAM,IAAA,CAAK,WAAW,CAAA;AAAA,IAChD;AAAA,EACF;AACF;AAKO,IAAM,gBAAA,GAAN,cAA+B,YAAA,CAAa;AAAA,EACjD,WAAA,CAAY,UAAU,yBAAA,EAA2B;AAC/C,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,kBAAA;AAAA,EACd;AACF;AAKO,IAAM,sBAAA,GAAN,cAAqC,YAAA,CAAa;AAAA,EAGvD,WAAA,CAAY,OAAA,GAAU,qBAAA,EAAuB,GAAA,EAAc;AACzD,IAAA,KAAA,CAAM,OAAO,CAAA;AAHf,IAAA,aAAA,CAAA,IAAA,EAAgB,KAAA,CAAA;AAId,IAAA,IAAA,CAAK,IAAA,GAAO,wBAAA;AACZ,IAAA,IAAA,CAAK,GAAA,GAAM,GAAA;AAAA,EACb;AACF;AAKO,IAAM,iBAAA,GAAN,cAAgC,YAAA,CAAa;AAAA,EAIlD,YACE,OAAA,GAAU,kBAAA,EACV,WAAA,GAAc,CAAA,EACd,cAAc,CAAA,EACd;AACA,IAAA,KAAA,CAAM,OAAO,CAAA;AARf,IAAA,aAAA,CAAA,IAAA,EAAgB,aAAA,CAAA;AAChB,IAAA,aAAA,CAAA,IAAA,EAAgB,aAAA,CAAA;AAQd,IAAA,IAAA,CAAK,IAAA,GAAO,mBAAA;AACZ,IAAA,IAAA,CAAK,WAAA,GAAc,WAAA;AACnB,IAAA,IAAA,CAAK,WAAA,GAAc,WAAA;AAAA,EACrB;AACF;AAKO,IAAM,YAAA,GAAN,cAA2B,YAAA,CAAa;AAAA,EAG7C,WAAA,CAAY,OAAA,GAAU,qBAAA,EAAuB,SAAA,GAAY,CAAA,EAAG;AAC1D,IAAA,KAAA,CAAM,OAAO,CAAA;AAHf,IAAA,aAAA,CAAA,IAAA,EAAgB,WAAA,CAAA;AAId,IAAA,IAAA,CAAK,IAAA,GAAO,cAAA;AACZ,IAAA,IAAA,CAAK,SAAA,GAAY,SAAA;AAAA,EACnB;AACF;AAKO,IAAM,uBAAA,GAAN,cAAsC,YAAA,CAAa;AAAA,EAIxD,WAAA,CACE,OAAA,GAAU,gCAAA,EACV,QAAA,GAAW,GACX,SAAA,EACA;AACA,IAAA,KAAA,CAAM,OAAO,CAAA;AARf,IAAA,aAAA,CAAA,IAAA,EAAgB,UAAA,CAAA;AAChB,IAAA,aAAA,CAAA,IAAA,EAAgB,WAAA,CAAA;AAQd,IAAA,IAAA,CAAK,IAAA,GAAO,yBAAA;AACZ,IAAA,IAAA,CAAK,QAAA,GAAW,QAAA;AAChB,IAAA,IAAA,CAAK,SAAA,GAAY,SAAA;AAAA,EACnB;AACF;AAKO,IAAM,mBAAA,GAAN,cAAkC,YAAA,CAAa;AAAA,EACpD,WAAA,CAAY,UAAU,oBAAA,EAAsB;AAC1C,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,qBAAA;AAAA,EACd;AACF;AAYO,SAAS,iBAAiB,KAAA,EAAiD;AAChF,EAAA,OACE,iBAAiB,KAAA,IACjB,WAAA,IAAe,KAAA,IACf,OAAQ,MAAyB,SAAA,KAAc,SAAA;AAEnD;AAKO,IAAM,qBAAA,GAAN,cAAoC,KAAA,CAAgC;AAAA,EAIzE,WAAA,CAAY,OAAc,SAAA,EAAoB;AAC5C,IAAA,KAAA,CAAM,KAAA,CAAM,OAAA,EAAS,EAAE,KAAA,EAAO,OAAO,CAAA;AAJvC,IAAA,aAAA,CAAA,IAAA,EAAgB,WAAA,CAAA;AAChB,IAAA,aAAA,CAAA,IAAA,EAAyB,OAAA,CAAA;AAIvB,IAAA,IAAA,CAAK,IAAA,GAAO,uBAAA;AACZ,IAAA,IAAA,CAAK,SAAA,GAAY,SAAA;AACjB,IAAA,IAAA,CAAK,KAAA,GAAQ,KAAA;AACb,IAAA,IAAI,MAAM,KAAA,EAAO;AACf,MAAA,IAAA,CAAK,QAAQ,KAAA,CAAM,KAAA;AAAA,IACrB;AAAA,EACF;AACF;AAKO,SAAS,WAAA,CAAY,KAAA,EAAc,SAAA,GAAY,IAAA,EAA6B;AACjF,EAAA,OAAO,IAAI,qBAAA,CAAsB,KAAA,EAAO,SAAS,CAAA;AACnD;AAKO,SAAS,eAAe,KAAA,EAAqC;AAClE,EAAA,OAAO,IAAI,qBAAA,CAAsB,KAAA,EAAO,KAAK,CAAA;AAC/C;AAOO,SAAS,YAAY,KAAA,EAAqC;AAC/D,EAAA,IAAI,gBAAA,CAAiB,KAAK,CAAA,EAAG;AAC3B,IAAA,OAAO,KAAA,CAAM,SAAA;AAAA,EACf;AACA,EAAA,OAAO,MAAA;AACT;;;AC7GO,IAAM,UAAA,GAA4B;AAAA,EACvC,OAAO,MAAM,MAAA;AAAA,EACb,MAAM,MAAM,MAAA;AAAA,EACZ,MAAM,MAAM,MAAA;AAAA,EACZ,OAAO,MAAM;AACf;AAMO,IAAM,aAAA,GAA+B;AAAA,EAC1C,KAAA,EAAO,CAAC,GAAA,EAAK,OAAA,KAAY;AACvB,IAAA,IAAI,OAAA,EAAS;AACX,MAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,UAAA,EAAa,GAAG,CAAA,CAAA,EAAI,OAAO,CAAA;AAAA,IAC3C,CAAA,MAAO;AACL,MAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,UAAA,EAAa,GAAG,CAAA,CAAE,CAAA;AAAA,IAClC;AAAA,EACF,CAAA;AAAA,EACA,IAAA,EAAM,CAAC,GAAA,EAAK,OAAA,KAAY;AACtB,IAAA,IAAI,OAAA,EAAS;AACX,MAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,UAAA,EAAa,GAAG,CAAA,CAAA,EAAI,OAAO,CAAA;AAAA,IAC1C,CAAA,MAAO;AACL,MAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,UAAA,EAAa,GAAG,CAAA,CAAE,CAAA;AAAA,IACjC;AAAA,EACF,CAAA;AAAA,EACA,IAAA,EAAM,CAAC,GAAA,EAAK,OAAA,KAAY;AACtB,IAAA,IAAI,OAAA,EAAS;AACX,MAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,UAAA,EAAa,GAAG,CAAA,CAAA,EAAI,OAAO,CAAA;AAAA,IAC1C,CAAA,MAAO;AACL,MAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,UAAA,EAAa,GAAG,CAAA,CAAE,CAAA;AAAA,IACjC;AAAA,EACF,CAAA;AAAA,EACA,KAAA,EAAO,CAAC,GAAA,EAAK,OAAA,KAAY;AACvB,IAAA,IAAI,OAAA,EAAS;AACX,MAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,UAAA,EAAa,GAAG,CAAA,CAAA,EAAI,OAAO,CAAA;AAAA,IAC3C,CAAA,MAAO;AACL,MAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,UAAA,EAAa,GAAG,CAAA,CAAE,CAAA;AAAA,IAClC;AAAA,EACF;AACF;;;ACjFO,SAAS,KAAA,CAAM,IAAY,MAAA,EAAqC;AACrE,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACtC,IAAA,IAAI,QAAQ,OAAA,EAAS;AACnB,MAAA,MAAA,CAAO,WAAA,CAAY,MAAA,CAAO,MAAM,CAAC,CAAA;AACjC,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,SAAA,GAAY,UAAA,CAAW,OAAA,EAAS,EAAE,CAAA;AAExC,IAAA,MAAA,EAAQ,gBAAA;AAAA,MACN,OAAA;AAAA,MACA,MAAM;AACJ,QAAA,YAAA,CAAa,SAAS,CAAA;AACtB,QAAA,MAAA,CAAO,WAAA,CAAY,MAAA,CAAO,MAAM,CAAC,CAAA;AAAA,MACnC,CAAA;AAAA,MACA,EAAE,MAAM,IAAA;AAAK,KACf;AAAA,EACF,CAAC,CAAA;AACH;AAOA,SAAS,YAAY,MAAA,EAAwB;AAC3C,EAAA,IAAI,kBAAkB,KAAA,EAAO;AAC3B,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,IAAI,OAAO,WAAW,QAAA,EAAU;AAC9B,IAAA,OAAO,IAAI,MAAM,MAAM,CAAA;AAAA,EACzB;AACA,EAAA,IAAI,MAAA,KAAW,MAAA,IAAa,MAAA,KAAW,IAAA,EAAM;AAE3C,IAAA,IAAI;AACF,MAAA,OAAO,IAAI,KAAA,CAAM,IAAA,CAAK,SAAA,CAAU,MAAM,CAAC,CAAA;AAAA,IACzC,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,IAAI,MAAM,eAAe,CAAA;AAAA,IAClC;AAAA,EACF;AACA,EAAA,OAAO,IAAI,YAAA,CAAa,SAAA,EAAW,YAAY,CAAA;AACjD;AAWA,eAAsB,WAAA,CACpB,OAAA,EACA,EAAA,EACA,MAAA,EACY;AAEZ,EAAA,IAAI,QAAQ,OAAA,EAAS;AACnB,IAAA,MAAM,WAAA,CAAY,OAAO,MAAM,CAAA;AAAA,EACjC;AAEA,EAAA,IAAI,SAAA;AAEJ,EAAA,MAAM,cAAA,GAAiB,IAAI,OAAA,CAAe,CAAC,GAAG,MAAA,KAAW;AACvD,IAAA,SAAA,GAAY,WAAW,MAAM;AAC3B,MAAA,MAAA,CAAO,IAAI,aAAa,CAAA,0BAAA,EAA6B,MAAA,CAAO,EAAE,CAAC,CAAA,EAAA,CAAA,EAAM,EAAE,CAAC,CAAA;AAAA,IAC1E,GAAG,EAAE,CAAA;AAAA,EACP,CAAC,CAAA;AAGD,EAAA,MAAM,eAAe,MAAA,GACjB,IAAI,OAAA,CAAe,CAAC,GAAG,MAAA,KAAW;AAChC,IAAA,MAAA,CAAO,gBAAA;AAAA,MACL,OAAA;AAAA,MACA,MAAM,MAAA,CAAO,WAAA,CAAY,MAAA,CAAO,MAAM,CAAC,CAAA;AAAA,MACvC,EAAE,MAAM,IAAA;AAAK,KACf;AAAA,EACF,CAAC,CAAA,GACD,IAAA;AAEJ,EAAA,IAAI;AACF,IAAA,MAAM,MAAA,GAAuB,CAAC,OAAA,EAAS,cAAc,CAAA;AACrD,IAAA,IAAI,YAAA,EAAc;AAChB,MAAA,MAAA,CAAO,KAAK,YAAY,CAAA;AAAA,IAC1B;AACA,IAAA,OAAO,MAAM,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAA;AAAA,EAClC,CAAA,SAAE;AACA,IAAA,IAAI,cAAc,MAAA,EAAW;AAC3B,MAAA,YAAA,CAAa,SAAS,CAAA;AAAA,IACxB;AAAA,EACF;AACF;AAWA,eAAsB,kBAAA,CACpB,SAAA,EACA,EAAA,EACA,MAAA,EACY;AACZ,EAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AAGvC,EAAA,MAAM,cAAA,GAAiB,cAAA,CAAe,MAAA,EAAQ,UAAA,CAAW,MAAM,CAAA;AAE/D,EAAA,MAAM,SAAA,GAAY,WAAW,MAAM;AACjC,IAAA,UAAA,CAAW,KAAA,CAAM,IAAI,YAAA,CAAa,CAAA,0BAAA,EAA6B,OAAO,EAAE,CAAC,CAAA,EAAA,CAAA,EAAM,EAAE,CAAC,CAAA;AAAA,EACpF,GAAG,EAAE,CAAA;AAEL,EAAA,IAAI;AACF,IAAA,OAAO,MAAM,UAAU,cAAc,CAAA;AAAA,EACvC,CAAA,SAAE;AACA,IAAA,YAAA,CAAa,SAAS,CAAA;AAAA,EACxB;AACF;AAYO,SAAS,kBAAkB,OAAA,EAAmD;AACnF,EAAA,MAAM,eAAe,OAAA,CAAQ,MAAA,CAAO,CAAC,CAAA,KAAwB,MAAM,MAAS,CAAA;AAE5E,EAAA,IAAI,YAAA,CAAa,WAAW,CAAA,EAAG;AAC7B,IAAA,OAAO,IAAI,iBAAgB,CAAE,MAAA;AAAA,EAC/B;AAEA,EAAA,MAAM,WAAA,GAAc,aAAa,CAAC,CAAA;AAClC,EAAA,IAAI,YAAA,CAAa,MAAA,KAAW,CAAA,IAAK,WAAA,EAAa;AAC5C,IAAA,OAAO,WAAA;AAAA,EACT;AAGA,EAAA,IAAI,SAAS,WAAA,EAAa;AACxB,IAAA,OAAO,WAAA,CAAY,IAAI,YAAY,CAAA;AAAA,EACrC;AAIA,EAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,EAAA,MAAM,YAA6D,EAAC;AAGpE,EAAA,MAAM,UAAU,MAAM;AACpB,IAAA,KAAA,MAAW,EAAE,MAAA,EAAQ,QAAA,EAAS,IAAK,SAAA,EAAW;AAC5C,MAAA,MAAA,CAAO,mBAAA,CAAoB,SAAS,QAAQ,CAAA;AAAA,IAC9C;AACA,IAAA,SAAA,CAAU,MAAA,GAAS,CAAA;AAAA,EACrB,CAAA;AAEA,EAAA,KAAA,MAAW,UAAU,YAAA,EAAc;AACjC,IAAA,IAAI,OAAO,OAAA,EAAS;AAClB,MAAA,UAAA,CAAW,KAAA,CAAM,OAAO,MAAM,CAAA;AAC9B,MAAA,OAAO,UAAA,CAAW,MAAA;AAAA,IACpB;AAEA,IAAA,MAAM,WAAW,MAAM;AACrB,MAAA,OAAA,EAAQ;AACR,MAAA,UAAA,CAAW,KAAA,CAAM,OAAO,MAAM,CAAA;AAAA,IAChC,CAAA;AAEA,IAAA,SAAA,CAAU,IAAA,CAAK,EAAE,MAAA,EAAQ,QAAA,EAAU,CAAA;AACnC,IAAA,MAAA,CAAO,iBAAiB,OAAA,EAAS,QAAA,EAAU,EAAE,IAAA,EAAM,MAAM,CAAA;AAAA,EAC3D;AAEA,EAAA,OAAO,UAAA,CAAW,MAAA;AACpB;AAQO,SAAS,aAAa,KAAA,EAAyB;AACpD,EAAA,OACE,KAAA,YAAiB,YAAA,IACjB,KAAA,CAAM,IAAA,KAAS,YAAA;AAEnB;AASO,SAAS,eAAe,MAAA,EAAuC;AACpE,EAAA,IAAI,QAAQ,OAAA,EAAS;AACnB,IAAA,MAAM,MAAA,CAAO,MAAA,IAAU,IAAI,YAAA,CAAa,WAAW,YAAY,CAAA;AAAA,EACjE;AACF;AASO,SAAS,YAAA,CACd,UACA,OAAA,EACe;AACf,EAAA,IAAI,CAAC,UAAU,OAAO,MAAA;AAEtB,EAAA,QAAQ,IAAI,IAAA,KAAmD;AAC7D,IAAA,IAAI;AACF,MAAA,OAAO,QAAA,CAAS,GAAG,IAAI,CAAA;AAAA,IACzB,SAAS,KAAA,EAAO;AACd,MAAA,IAAI,OAAA,IAAW,iBAAiB,KAAA,EAAO;AACrC,QAAA,OAAA,CAAQ,KAAK,CAAA;AAAA,MACf;AACA,MAAA,OAAO,MAAA;AAAA,IACT;AAAA,EACF,CAAA;AACF;AASO,SAAS,UAAU,KAAA,EAAuB;AAC/C,EAAA,MAAM,YAAA,GAAe,CAAA,GAAI,IAAA,CAAK,MAAA,EAAO,GAAI,GAAA;AACzC,EAAA,OAAO,IAAA,CAAK,KAAA,CAAM,KAAA,GAAQ,YAAY,CAAA;AACxC;AAUO,SAAS,KAAA,CAAM,KAAA,EAAe,GAAA,EAAa,GAAA,EAAqB;AACrE,EAAA,OAAO,KAAK,GAAA,CAAI,IAAA,CAAK,IAAI,KAAA,EAAO,GAAG,GAAG,GAAG,CAAA;AAC3C;AAQO,SAAS,GAAA,GAAc;AAC5B,EAAA,IAAI,OAAO,WAAA,KAAgB,WAAA,IAAe,OAAO,WAAA,CAAY,QAAQ,UAAA,EAAY;AAC/E,IAAA,OAAO,YAAY,GAAA,EAAI;AAAA,EACzB;AACA,EAAA,OAAO,KAAK,GAAA,EAAI;AAClB;AC/QA,IAAM,cAAA,GAAiBA,MAAE,QAAA,EAAS;AAM3B,IAAM,YAAA,GAAeA,MAAE,MAAA,CAAO;AAAA,EACnC,KAAA,EAAO,cAAA;AAAA,EACP,IAAA,EAAM,cAAA;AAAA,EACN,IAAA,EAAM,cAAA;AAAA,EACN,KAAA,EAAO;AACT,CAAC,EAAE,QAAA;AAKI,IAAM,mBAAA,GAAsBA,MAAE,MAAA,CAAO;AAAA;AAAA,EAE1C,cAAA,EAAgBA,MAAE,MAAA,EAAO,CAAE,KAAI,CAAE,QAAA,EAAS,CAAE,OAAA,CAAQ,GAAK,CAAA;AAAA;AAAA,EAEzD,SAAA,EAAW,eAAe,QAAA,EAAS;AAAA;AAAA,EAEnC,MAAA,EAAQ;AACV,CAAC;AAOM,IAAM,sBAAsBA,KAAA,CAAE,IAAA,CAAK,CAAC,aAAA,EAAe,QAAA,EAAU,UAAU,CAAC;AAOxE,IAAM,iBAAA,GAAoBA,MAAE,MAAA,CAAO;AAAA;AAAA,EAExC,WAAA,EAAaA,MAAE,MAAA,EAAO,CAAE,KAAI,CAAE,QAAA,EAAS,CAAE,OAAA,CAAQ,CAAC,CAAA;AAAA;AAAA,EAElD,YAAA,EAAcA,MAAE,MAAA,EAAO,CAAE,KAAI,CAAE,QAAA,EAAS,CAAE,OAAA,CAAQ,GAAG,CAAA;AAAA;AAAA,EAErD,QAAA,EAAUA,MAAE,MAAA,EAAO,CAAE,KAAI,CAAE,QAAA,GAAW,QAAA,EAAS;AAAA;AAAA,EAE/C,aAAA,EAAe,mBAAA,CAAoB,OAAA,CAAQ,aAAa,CAAA;AAAA;AAAA,EAExD,YAAYA,KAAA,CAAE,MAAA,GAAS,QAAA,EAAS,CAAE,QAAQ,CAAG,CAAA;AAAA;AAAA,EAE7C,MAAA,EAAQA,KAAA,CAAE,OAAA,EAAQ,CAAE,QAAQ,KAAK,CAAA;AAAA;AAAA,EAEjC,WAAA,EAAa,eAAe,QAAA,EAAS;AAAA;AAAA,EAErC,OAAA,EAAS,eAAe,QAAA,EAAS;AAAA;AAAA,EAEjC,MAAA,EAAQ;AACV,CAAC;AAOM,IAAM,4BAA4BA,KAAA,CAAE,IAAA,CAAK,CAAC,QAAA,EAAU,MAAA,EAAQ,WAAW,CAAC;AAOxE,IAAM,0BAAA,GAA6BA,MAAE,MAAA,CAAO;AAAA,EACjD,UAAUA,KAAA,CAAE,MAAA,EAAO,CAAE,GAAA,GAAM,WAAA,EAAY;AAAA,EACvC,gBAAgBA,KAAA,CAAE,MAAA,EAAO,CAAE,GAAA,GAAM,WAAA,EAAY;AAAA,EAC7C,eAAeA,KAAA,CAAE,MAAA,EAAO,CAAE,GAAA,GAAM,WAAA,EAAY;AAAA,EAC5C,sBAAsBA,KAAA,CAAE,MAAA,EAAO,CAAE,GAAA,GAAM,WAAA,EAAY;AAAA,EACnD,qBAAqBA,KAAA,CAAE,MAAA,EAAO,CAAE,GAAA,GAAM,WAAA;AACxC,CAAC;AAOM,IAAM,0BAAA,GAA6BA,MAAE,MAAA,CAAO;AAAA;AAAA,EAEjD,WAAA,EAAaA,MAAE,MAAA,EAAO,CAAE,KAAI,CAAE,QAAA,EAAS,CAAE,OAAA,CAAQ,CAAC,CAAA;AAAA;AAAA,EAElD,OAAA,EAASA,MAAE,MAAA,EAAO,CAAE,KAAI,CAAE,QAAA,EAAS,CAAE,OAAA,CAAQ,GAAK,CAAA;AAAA;AAAA,EAElD,mBAAA,EAAqBA,MAAE,MAAA,EAAO,CAAE,KAAI,CAAE,QAAA,EAAS,CAAE,OAAA,CAAQ,CAAC,CAAA;AAAA;AAAA,EAE1D,QAAA,EAAUA,MAAE,MAAA,EAAO,CAAE,KAAI,CAAE,WAAA,EAAY,CAAE,OAAA,CAAQ,CAAC,CAAA;AAAA;AAAA,EAElD,WAAA,EAAa,eAAe,QAAA,EAAS;AAAA;AAAA,EAErC,YAAA,EAAc,eAAe,QAAA,EAAS;AAAA;AAAA,EAEtC,aAAA,EAAe,eAAe,QAAA,EAAS;AAAA;AAAA,EAEvC,MAAA,EAAQ;AACV,CAAC;AAOM,IAAM,qBAAA,GAAwBA,MAAE,MAAA,CAAO;AAAA;AAAA,EAE5C,IAAA,EAAMA,MAAE,MAAA,EAAO,CAAE,KAAI,CAAE,QAAA,EAAS,CAAE,OAAA,CAAQ,GAAG,CAAA;AAAA;AAAA,EAE7C,KAAA,EAAOA,MAAE,MAAA,EAAO,CAAE,KAAI,CAAE,QAAA,GAAW,QAAA,EAAS;AAAA;AAAA,EAE5C,QAAA,EAAUA,MAAE,MAAA,EAAO,CAAE,KAAI,CAAE,QAAA,EAAS,CAAE,OAAA,CAAQ,GAAI,CAAA;AAAA;AAAA,EAElD,OAAA,EAAS,eAAe,QAAA,EAAS;AAAA;AAAA,EAEjC,MAAA,EAAQ;AACV,CAAC;AAOM,IAAM,oBAAA,GAAuBA,MAAE,MAAA,CAAO;AAAA;AAAA,EAE3C,aAAA,EAAeA,MAAE,MAAA,EAAO,CAAE,KAAI,CAAE,QAAA,EAAS,CAAE,OAAA,CAAQ,EAAE,CAAA;AAAA;AAAA,EAErD,QAAA,EAAUA,MAAE,MAAA,EAAO,CAAE,KAAI,CAAE,WAAA,EAAY,CAAE,OAAA,CAAQ,CAAC,CAAA;AAAA;AAAA,EAElD,YAAA,EAAcA,MAAE,MAAA,EAAO,CAAE,KAAI,CAAE,QAAA,GAAW,QAAA,EAAS;AAAA;AAAA,EAEnD,UAAA,EAAY,eAAe,QAAA,EAAS;AAAA;AAAA,EAEpC,MAAA,EAAQ;AACV,CAAC;AAOM,IAAM,oBAAA,GAAuBA,MAAE,MAAA,CAAO;AAAA;AAAA,EAE3C,QAAA,EAAU,cAAA;AAAA;AAAA,EAEV,cAAA,EAAgB,eAAe,QAAA,EAAS;AAAA;AAAA,EAExC,UAAA,EAAY,eAAe,QAAA,EAAS;AAAA;AAAA,EAEpC,SAAA,EAAW,eAAe,QAAA,EAAS;AAAA;AAAA,EAEnC,MAAA,EAAQ;AACV,CAAC;AC7JD,IAAM,UAAA,GAAa,GAAA;AAGnB,IAAM,aAAA,GAAgB,UAAA;AAOf,IAAM,iBAAA,GAAoBA,MAAE,MAAA,CAAO;AAAA,EACxC,QAAQA,KAAAA,CAAE,MAAA,GAAS,WAAA,EAAY,CAAE,IAAI,UAAU,CAAA;AAAA,EAC/C,UAAA,EAAYA,MAAE,MAAA,EAAO,CAAE,KAAI,CAAE,WAAA,EAAY,CAAE,GAAA,CAAI,aAAa;AAC9D,CAAC;AA6BM,SAAS,oBAAoB,IAAA,EAAmC;AACrE,EAAA,MAAM,MAAA,GAAS,iBAAA,CAAkB,SAAA,CAAU,IAAI,CAAA;AAC/C,EAAA,OAAO,MAAA,CAAO,OAAA,GAAU,MAAA,CAAO,IAAA,GAAO,IAAA;AACxC;AAaA,IAAM,cAAA,GAAiB,GAAA;AAIvB,IAAM,mBAAA,GAAsB,kBAAA;AAG5B,IAAM,qBAAA,GAAwB,QAAA;AAiBvB,SAAS,mBAAmB,GAAA,EAAqB;AAEtD,EAAA,MAAM,SAAA,GAAY,IAAI,MAAA,GAAS,cAAA,GAAiB,IAAI,KAAA,CAAM,CAAA,EAAG,cAAc,CAAA,GAAI,GAAA;AAI/E,EAAA,OAAO,UACJ,OAAA,CAAQ,mBAAA,EAAqB,EAAE,CAAA,CAC/B,OAAA,CAAQ,uBAAuB,GAAG,CAAA;AACvC;AA0KO,IAAM,cAAA,GAAN,MAAM,cAAA,CAA0C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBrD,YAAY,OAAA,EAA4C;AAVxD,IAAA,aAAA,CAAA,IAAA,EAAiB,SAAA,sBAAc,GAAA,EAAyB,CAAA;AACxD,IAAA,aAAA,CAAA,IAAA,EAAiB,YAAA,CAAA;AACjB,IAAA,aAAA,CAAA,IAAA,EAAQ,eAAA,EAAgB,CAAA,CAAA;AAStB,IAAA,IAAA,CAAK,UAAA,GAAa,SAAS,UAAA,IAAc,GAAA;AAAA,EAC3C;AAAA,EAEA,IAAI,GAAA,EAA0C;AAC5C,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,OAAA,CAAQ,GAAG,CAAA;AAC9B,IAAA,OAAO,UAAU,IAAA,GAAO,cAAA,CAAc,aAAA,GAAgB,OAAA,CAAQ,QAAQ,KAAK,CAAA;AAAA,EAC7E;AAAA,EAEA,GAAA,CAAI,KAAa,KAAA,EAAmC;AAClD,IAAA,IAAA,CAAK,OAAA,CAAQ,KAAK,KAAK,CAAA;AACvB,IAAA,OAAO,cAAA,CAAc,aAAA;AAAA,EACvB;AAAA,EAEA,aAAA,CACE,GAAA,EACA,QAAA,EACA,QAAA,EAC8B;AAC9B,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,GAAG,CAAA,IAAK,IAAA;AAGzC,IAAA,MAAM,OAAA,GACJ,OAAA,KAAY,QAAA,IACX,OAAA,KAAY,IAAA,IACX,QAAA,KAAa,IAAA,IACb,OAAA,CAAQ,MAAA,KAAW,QAAA,CAAS,MAAA,IAC5B,OAAA,CAAQ,eAAe,QAAA,CAAS,UAAA;AAEpC,IAAA,IAAI,OAAA,EAAS;AACX,MAAA,IAAA,CAAK,OAAA,CAAQ,KAAK,QAAQ,CAAA;AAC1B,MAAA,OAAO,QAAQ,OAAA,CAAQ,EAAE,SAAS,IAAA,EAAM,YAAA,EAAc,UAAU,CAAA;AAAA,IAClE;AAEA,IAAA,OAAO,QAAQ,OAAA,CAAQ,EAAE,SAAS,KAAA,EAAO,YAAA,EAAc,SAAS,CAAA;AAAA,EAClE;AAAA,EAEA,OAAO,GAAA,EAA4B;AACjC,IAAA,IAAA,CAAK,WAAW,GAAG,CAAA;AACnB,IAAA,OAAO,cAAA,CAAc,aAAA;AAAA,EACvB;AAAA,EAEA,KAAA,GAAuB;AACrB,IAAA,IAAA,CAAK,SAAA,EAAU;AACf,IAAA,OAAO,cAAA,CAAc,aAAA;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,IAAA,GAAe;AACb,IAAA,OAAO,KAAK,OAAA,CAAQ,IAAA;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAA,GAA2B;AACzB,IAAA,OAAO,IAAA,CAAK,aAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,QAAQ,GAAA,EAAiC;AACvC,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,GAAG,CAAA;AAClC,IAAA,IAAI,KAAA,EAAO;AAET,MAAA,IAAA,CAAK,OAAA,CAAQ,OAAO,GAAG,CAAA;AACvB,MAAA,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,GAAA,EAAK,KAAK,CAAA;AAAA,IAC7B;AACA,IAAA,OAAO,KAAA,IAAS,IAAA;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAA,CAAQ,KAAa,KAAA,EAA0B;AAC7C,IAAA,IAAI,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,GAAG,CAAA,EAAG;AACzB,MAAA,IAAA,CAAK,OAAA,CAAQ,OAAO,GAAG,CAAA;AAAA,IACzB,CAAA,MAAA,IAAW,KAAK,UAAA,GAAa,CAAA,IAAK,KAAK,OAAA,CAAQ,IAAA,IAAQ,KAAK,UAAA,EAAY;AACtE,MAAA,MAAM,WAAW,IAAA,CAAK,OAAA,CAAQ,IAAA,EAAK,CAAE,MAAK,CAAE,KAAA;AAC5C,MAAA,IAAI,aAAa,MAAA,EAAW;AAC1B,QAAA,IAAA,CAAK,OAAA,CAAQ,OAAO,QAAQ,CAAA;AAC5B,QAAA,IAAA,CAAK,aAAA,EAAA;AAAA,MACP;AAAA,IACF;AACA,IAAA,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,GAAA,EAAK,KAAK,CAAA;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,WAAW,GAAA,EAAmB;AAC5B,IAAA,IAAA,CAAK,OAAA,CAAQ,OAAO,GAAG,CAAA;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,SAAA,GAAkB;AAChB,IAAA,IAAA,CAAK,QAAQ,KAAA,EAAM;AAAA,EACrB;AACF,CAAA;AAAA;AA1HE,aAAA,CAFW,cAAA,EAEa,eAAA,EAAgB,OAAA,CAAQ,OAAA,EAAQ,CAAA;AAAA;AAGxD,aAAA,CALW,cAAA,EAKa,eAAA,EAA6C,OAAA,CAAQ,OAAA,CAAQ,IAAI,CAAA,CAAA;AALpF,IAAM,aAAA,GAAN","file":"index.cjs","sourcesContent":["/**\n * Base error class for all Fortify errors.\n */\nexport class FortifyError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'FortifyError';\n // Maintains proper stack trace for where our error was thrown (only available on V8)\n // Use typeof check to avoid unnecessary-condition lint error\n if (typeof Error.captureStackTrace === 'function') {\n Error.captureStackTrace(this, this.constructor);\n }\n }\n}\n\n/**\n * Error thrown when circuit breaker is in open state and rejecting requests.\n */\nexport class CircuitOpenError extends FortifyError {\n constructor(message = 'Circuit breaker is open') {\n super(message);\n this.name = 'CircuitOpenError';\n }\n}\n\n/**\n * Error thrown when rate limit is exceeded.\n */\nexport class RateLimitExceededError extends FortifyError {\n public readonly key: string | undefined;\n\n constructor(message = 'Rate limit exceeded', key?: string) {\n super(message);\n this.name = 'RateLimitExceededError';\n this.key = key;\n }\n}\n\n/**\n * Error thrown when bulkhead is at capacity.\n */\nexport class BulkheadFullError extends FortifyError {\n public readonly activeCount: number;\n public readonly queuedCount: number;\n\n constructor(\n message = 'Bulkhead is full',\n activeCount = 0,\n queuedCount = 0\n ) {\n super(message);\n this.name = 'BulkheadFullError';\n this.activeCount = activeCount;\n this.queuedCount = queuedCount;\n }\n}\n\n/**\n * Error thrown when operation times out.\n */\nexport class TimeoutError extends FortifyError {\n public readonly timeoutMs: number;\n\n constructor(message = 'Operation timed out', timeoutMs = 0) {\n super(message);\n this.name = 'TimeoutError';\n this.timeoutMs = timeoutMs;\n }\n}\n\n/**\n * Error thrown when maximum retry attempts are reached.\n */\nexport class MaxAttemptsReachedError extends FortifyError {\n public readonly attempts: number;\n public readonly lastError: Error | undefined;\n\n constructor(\n message = 'Maximum retry attempts reached',\n attempts = 0,\n lastError?: Error\n ) {\n super(message);\n this.name = 'MaxAttemptsReachedError';\n this.attempts = attempts;\n this.lastError = lastError;\n }\n}\n\n/**\n * Error thrown when bulkhead is closed and no new operations accepted.\n */\nexport class BulkheadClosedError extends FortifyError {\n constructor(message = 'Bulkhead is closed') {\n super(message);\n this.name = 'BulkheadClosedError';\n }\n}\n\n/**\n * Interface for errors that can indicate whether they are retryable.\n */\nexport interface RetryableError {\n retryable: boolean;\n}\n\n/**\n * Type guard to check if an error implements RetryableError interface.\n */\nexport function isRetryableError(error: unknown): error is Error & RetryableError {\n return (\n error instanceof Error &&\n 'retryable' in error &&\n typeof (error as RetryableError).retryable === 'boolean'\n );\n}\n\n/**\n * Wrapper class to mark an error as retryable.\n */\nexport class RetryableErrorWrapper extends Error implements RetryableError {\n public readonly retryable: boolean;\n public override readonly cause: Error;\n\n constructor(error: Error, retryable: boolean) {\n super(error.message, { cause: error });\n this.name = 'RetryableErrorWrapper';\n this.retryable = retryable;\n this.cause = error;\n if (error.stack) {\n this.stack = error.stack;\n }\n }\n}\n\n/**\n * Wrap an error as retryable.\n */\nexport function asRetryable(error: Error, retryable = true): RetryableErrorWrapper {\n return new RetryableErrorWrapper(error, retryable);\n}\n\n/**\n * Wrap an error as non-retryable.\n */\nexport function asNonRetryable(error: Error): RetryableErrorWrapper {\n return new RetryableErrorWrapper(error, false);\n}\n\n/**\n * Check if an error is retryable.\n * Returns true if the error implements RetryableError and retryable is true.\n * Returns undefined if the error doesn't implement RetryableError (let caller decide).\n */\nexport function isRetryable(error: unknown): boolean | undefined {\n if (isRetryableError(error)) {\n return error.retryable;\n }\n return undefined;\n}\n","/**\n * Represents an async operation that can be executed with cancellation support.\n * The signal parameter allows the operation to be cancelled via AbortController.\n *\n * @template T - The return type of the operation\n */\nexport type Operation<T> = (signal: AbortSignal) => Promise<T>;\n\n/**\n * Callback for state changes in stateful patterns (e.g., circuit breaker).\n *\n * @template S - The state type\n */\nexport type StateChangeCallback<S> = (from: S, to: S) => void;\n\n/**\n * Callback for error events.\n */\nexport type ErrorCallback = (error: Error) => void;\n\n/**\n * Simple void callback.\n */\nexport type VoidCallback = () => void;\n\n/**\n * Callback for retry events.\n */\nexport type RetryCallback = (attempt: number, error: Error) => void;\n\n/**\n * Callback for rate limit events.\n */\nexport type RateLimitCallback = (key: string) => void;\n\n/**\n * Logger interface for structured logging across all patterns.\n * Compatible with pino, winston, console, and custom implementations.\n */\nexport interface FortifyLogger {\n debug(msg: string, context?: Record<string, unknown>): void;\n info(msg: string, context?: Record<string, unknown>): void;\n warn(msg: string, context?: Record<string, unknown>): void;\n error(msg: string, context?: Record<string, unknown>): void;\n}\n\n/**\n * No-operation logger that discards all log messages.\n * Useful for testing or when logging is not needed.\n */\nexport const noopLogger: FortifyLogger = {\n debug: () => undefined,\n info: () => undefined,\n warn: () => undefined,\n error: () => undefined,\n};\n\n/**\n * Console-based logger implementation.\n * Browser-friendly and works in all environments.\n */\nexport const consoleLogger: FortifyLogger = {\n debug: (msg, context) => {\n if (context) {\n console.debug(`[fortify] ${msg}`, context);\n } else {\n console.debug(`[fortify] ${msg}`);\n }\n },\n info: (msg, context) => {\n if (context) {\n console.info(`[fortify] ${msg}`, context);\n } else {\n console.info(`[fortify] ${msg}`);\n }\n },\n warn: (msg, context) => {\n if (context) {\n console.warn(`[fortify] ${msg}`, context);\n } else {\n console.warn(`[fortify] ${msg}`);\n }\n },\n error: (msg, context) => {\n if (context) {\n console.error(`[fortify] ${msg}`, context);\n } else {\n console.error(`[fortify] ${msg}`);\n }\n },\n};\n\n/**\n * Generic pattern interface that all resilience patterns implement.\n *\n * @template T - The return type of the operation\n */\nexport interface Pattern<T> {\n /**\n * Execute an operation with the pattern's resilience logic.\n *\n * @param operation - The async operation to execute\n * @param signal - Optional AbortSignal for cancellation\n * @returns Promise resolving to the operation result\n */\n execute(operation: Operation<T>, signal?: AbortSignal): Promise<T>;\n}\n\n/**\n * Interface for patterns that support closing/cleanup.\n */\nexport interface Closeable {\n /**\n * Close the pattern and release resources.\n * May wait for in-flight operations to complete.\n */\n close(): Promise<void>;\n}\n\n/**\n * Interface for patterns that support resetting state.\n */\nexport interface Resettable {\n /**\n * Reset the pattern to its initial state.\n */\n reset(): void;\n}\n","import { TimeoutError } from './errors.js';\n\n/**\n * Sleep for a specified duration with optional cancellation support.\n *\n * @param ms - Duration in milliseconds\n * @param signal - Optional AbortSignal for cancellation\n * @returns Promise that resolves after the delay or rejects if cancelled\n */\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(ensureError(signal.reason));\n return;\n }\n\n const timeoutId = setTimeout(resolve, ms);\n\n signal?.addEventListener(\n 'abort',\n () => {\n clearTimeout(timeoutId);\n reject(ensureError(signal.reason));\n },\n { once: true }\n );\n });\n}\n\n/**\n * Ensure a value is an Error instance.\n * @param reason - The reason to convert to an Error\n * @returns An Error instance\n */\nfunction ensureError(reason: unknown): Error {\n if (reason instanceof Error) {\n return reason;\n }\n if (typeof reason === 'string') {\n return new Error(reason);\n }\n if (reason !== undefined && reason !== null) {\n // Handle objects and other types safely\n try {\n return new Error(JSON.stringify(reason));\n } catch {\n return new Error('Unknown error');\n }\n }\n return new DOMException('Aborted', 'AbortError');\n}\n\n/**\n * Wrap a promise with a timeout.\n *\n * @template T - The promise return type\n * @param promise - The promise to wrap\n * @param ms - Timeout duration in milliseconds\n * @param signal - Optional external AbortSignal for cancellation\n * @returns Promise that resolves with the result or rejects with TimeoutError\n */\nexport async function withTimeout<T>(\n promise: Promise<T>,\n ms: number,\n signal?: AbortSignal\n): Promise<T> {\n // Check if already aborted\n if (signal?.aborted) {\n throw ensureError(signal.reason);\n }\n\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n reject(new TimeoutError(`Operation timed out after ${String(ms)}ms`, ms));\n }, ms);\n });\n\n // Handle external signal\n const abortPromise = signal\n ? new Promise<never>((_, reject) => {\n signal.addEventListener(\n 'abort',\n () => reject(ensureError(signal.reason)),\n { once: true }\n );\n })\n : null;\n\n try {\n const racers: Promise<T>[] = [promise, timeoutPromise];\n if (abortPromise) {\n racers.push(abortPromise);\n }\n return await Promise.race(racers);\n } finally {\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n }\n }\n}\n\n/**\n * Execute an operation with a timeout, passing the combined signal to the operation.\n *\n * @template T - The operation return type\n * @param operation - The operation to execute\n * @param ms - Timeout duration in milliseconds\n * @param signal - Optional external AbortSignal for cancellation\n * @returns Promise that resolves with the result or rejects with TimeoutError\n */\nexport async function executeWithTimeout<T>(\n operation: (signal: AbortSignal) => Promise<T>,\n ms: number,\n signal?: AbortSignal\n): Promise<T> {\n const controller = new AbortController();\n\n // Combine signals\n const combinedSignal = combineSignals(signal, controller.signal);\n\n const timeoutId = setTimeout(() => {\n controller.abort(new TimeoutError(`Operation timed out after ${String(ms)}ms`, ms));\n }, ms);\n\n try {\n return await operation(combinedSignal);\n } finally {\n clearTimeout(timeoutId);\n }\n}\n\n/**\n * Combine multiple AbortSignals into one.\n * Returns a signal that aborts when any of the input signals abort.\n *\n * Uses AbortSignal.any() when available (Node 20+, modern browsers).\n * Falls back to a manual implementation that properly cleans up event listeners.\n *\n * @param signals - AbortSignals to combine (undefined values are filtered out)\n * @returns Combined AbortSignal\n */\nexport function combineSignals(...signals: (AbortSignal | undefined)[]): AbortSignal {\n const validSignals = signals.filter((s): s is AbortSignal => s !== undefined);\n\n if (validSignals.length === 0) {\n return new AbortController().signal;\n }\n\n const firstSignal = validSignals[0];\n if (validSignals.length === 1 && firstSignal) {\n return firstSignal;\n }\n\n // Use AbortSignal.any if available (Node 20+, modern browsers)\n if ('any' in AbortSignal) {\n return AbortSignal.any(validSignals);\n }\n\n // Fallback: create a new controller and link it to all signals\n // Track listeners so we can clean them up to prevent memory leaks\n const controller = new AbortController();\n const listeners: { signal: AbortSignal; listener: () => void }[] = [];\n\n // Cleanup function to remove all listeners\n const cleanup = () => {\n for (const { signal, listener } of listeners) {\n signal.removeEventListener('abort', listener);\n }\n listeners.length = 0;\n };\n\n for (const signal of validSignals) {\n if (signal.aborted) {\n controller.abort(signal.reason);\n return controller.signal;\n }\n\n const listener = () => {\n cleanup(); // Clean up all listeners when any signal aborts\n controller.abort(signal.reason);\n };\n\n listeners.push({ signal, listener });\n signal.addEventListener('abort', listener, { once: true });\n }\n\n return controller.signal;\n}\n\n/**\n * Check if an error is an abort error (from AbortController).\n *\n * @param error - Error to check\n * @returns True if the error is an AbortError\n */\nexport function isAbortError(error: unknown): boolean {\n return (\n error instanceof DOMException &&\n error.name === 'AbortError'\n );\n}\n\n/**\n * Throw if the given signal is aborted.\n * Useful for checking signal state early in operations to avoid unnecessary work.\n *\n * @param signal - AbortSignal to check\n * @throws {DOMException} If signal is aborted (AbortError)\n */\nexport function throwIfAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted) {\n throw signal.reason ?? new DOMException('Aborted', 'AbortError');\n }\n}\n\n/**\n * Safely execute a callback, catching and logging any errors.\n * Used for user-provided callbacks to prevent them from breaking the pattern.\n *\n * @param callback - Callback to execute\n * @param onError - Optional error handler\n */\nexport function safeCallback<T extends (...args: unknown[]) => unknown>(\n callback: T | undefined,\n onError?: (error: Error) => void\n): T | undefined {\n if (!callback) return undefined;\n\n return ((...args: Parameters<T>): ReturnType<T> | undefined => {\n try {\n return callback(...args) as ReturnType<T>;\n } catch (error) {\n if (onError && error instanceof Error) {\n onError(error);\n }\n return undefined;\n }\n }) as T;\n}\n\n/**\n * Calculate jittered delay.\n * Adds 0-10% random variance to prevent thundering herd.\n *\n * @param delay - Base delay in milliseconds\n * @returns Jittered delay\n */\nexport function addJitter(delay: number): number {\n const jitterFactor = 1 + Math.random() * 0.1; // 0-10% jitter\n return Math.floor(delay * jitterFactor);\n}\n\n/**\n * Clamp a number between min and max values.\n *\n * @param value - Value to clamp\n * @param min - Minimum value\n * @param max - Maximum value\n * @returns Clamped value\n */\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n\n/**\n * Get current timestamp in milliseconds.\n * Uses performance.now() if available for higher precision.\n *\n * @returns Current timestamp in milliseconds\n */\nexport function now(): number {\n if (typeof performance !== 'undefined' && typeof performance.now === 'function') {\n return performance.now();\n }\n return Date.now();\n}\n","import { z } from 'zod';\n\n/**\n * Schema for a function type (Zod 4 compatible).\n * Uses z.function() which validates that the value is a function.\n */\nconst functionSchema = z.function();\n\n/**\n * Base schema for logger configuration.\n * Validates that the logger has the required methods.\n */\nexport const loggerSchema = z.object({\n debug: functionSchema,\n info: functionSchema,\n warn: functionSchema,\n error: functionSchema,\n}).optional();\n\n/**\n * Schema for timeout configuration.\n */\nexport const timeoutConfigSchema = z.object({\n /** Default timeout in milliseconds (default: 30000) */\n defaultTimeout: z.number().int().positive().default(30000),\n /** Callback when timeout occurs */\n onTimeout: functionSchema.optional(),\n /** Logger instance */\n logger: loggerSchema,\n});\n\nexport type TimeoutConfig = z.infer<typeof timeoutConfigSchema>;\n\n/**\n * Schema for retry backoff policy.\n */\nexport const backoffPolicySchema = z.enum(['exponential', 'linear', 'constant']);\n\nexport type BackoffPolicy = z.infer<typeof backoffPolicySchema>;\n\n/**\n * Schema for retry configuration.\n */\nexport const retryConfigSchema = z.object({\n /** Maximum number of attempts including the first (default: 3) */\n maxAttempts: z.number().int().positive().default(3),\n /** Initial delay before first retry in milliseconds (default: 100) */\n initialDelay: z.number().int().positive().default(100),\n /** Maximum delay between retries in milliseconds */\n maxDelay: z.number().int().positive().optional(),\n /** Backoff strategy (default: 'exponential') */\n backoffPolicy: backoffPolicySchema.default('exponential'),\n /** Multiplier for exponential backoff (default: 2.0) */\n multiplier: z.number().positive().default(2.0),\n /** Add random jitter to delays (default: false) */\n jitter: z.boolean().default(false),\n /** Custom function to determine if error is retryable */\n isRetryable: functionSchema.optional(),\n /** Callback on each retry attempt */\n onRetry: functionSchema.optional(),\n /** Logger instance */\n logger: loggerSchema,\n});\n\nexport type RetryConfig = z.infer<typeof retryConfigSchema>;\n\n/**\n * Schema for circuit breaker state.\n */\nexport const circuitBreakerStateSchema = z.enum(['closed', 'open', 'half-open']);\n\nexport type CircuitBreakerState = z.infer<typeof circuitBreakerStateSchema>;\n\n/**\n * Schema for circuit breaker counts/metrics.\n */\nexport const circuitBreakerCountsSchema = z.object({\n requests: z.number().int().nonnegative(),\n totalSuccesses: z.number().int().nonnegative(),\n totalFailures: z.number().int().nonnegative(),\n consecutiveSuccesses: z.number().int().nonnegative(),\n consecutiveFailures: z.number().int().nonnegative(),\n});\n\nexport type CircuitBreakerCounts = z.infer<typeof circuitBreakerCountsSchema>;\n\n/**\n * Schema for circuit breaker configuration.\n */\nexport const circuitBreakerConfigSchema = z.object({\n /** Maximum consecutive failures before opening (default: 5) */\n maxFailures: z.number().int().positive().default(5),\n /** Duration in open state before transitioning to half-open in milliseconds (default: 60000) */\n timeout: z.number().int().positive().default(60000),\n /** Maximum requests allowed in half-open state (default: 1) */\n halfOpenMaxRequests: z.number().int().positive().default(1),\n /** Period to clear counts when closed, 0 means never (default: 0) */\n interval: z.number().int().nonnegative().default(0),\n /** Custom function to determine when to trip the breaker */\n readyToTrip: functionSchema.optional(),\n /** Custom function to determine if result is successful */\n isSuccessful: functionSchema.optional(),\n /** Callback on state change */\n onStateChange: functionSchema.optional(),\n /** Logger instance */\n logger: loggerSchema,\n});\n\nexport type CircuitBreakerConfig = z.infer<typeof circuitBreakerConfigSchema>;\n\n/**\n * Schema for rate limiter configuration.\n */\nexport const rateLimitConfigSchema = z.object({\n /** Number of tokens added per interval (default: 100) */\n rate: z.number().int().positive().default(100),\n /** Maximum bucket capacity (defaults to rate) */\n burst: z.number().int().positive().optional(),\n /** Interval for token refill in milliseconds (default: 1000) */\n interval: z.number().int().positive().default(1000),\n /** Callback when rate limit is hit */\n onLimit: functionSchema.optional(),\n /** Logger instance */\n logger: loggerSchema,\n});\n\nexport type RateLimitConfig = z.infer<typeof rateLimitConfigSchema>;\n\n/**\n * Schema for bulkhead configuration.\n */\nexport const bulkheadConfigSchema = z.object({\n /** Maximum concurrent executions (default: 10) */\n maxConcurrent: z.number().int().positive().default(10),\n /** Maximum queued requests, 0 means no queueing (default: 0) */\n maxQueue: z.number().int().nonnegative().default(0),\n /** Maximum time to wait in queue in milliseconds */\n queueTimeout: z.number().int().positive().optional(),\n /** Callback when request is rejected */\n onRejected: functionSchema.optional(),\n /** Logger instance */\n logger: loggerSchema,\n});\n\nexport type BulkheadConfig = z.infer<typeof bulkheadConfigSchema>;\n\n/**\n * Schema for fallback configuration.\n */\nexport const fallbackConfigSchema = z.object({\n /** Fallback function to execute when primary fails */\n fallback: functionSchema,\n /** Custom function to determine if fallback should be used */\n shouldFallback: functionSchema.optional(),\n /** Callback when fallback is triggered */\n onFallback: functionSchema.optional(),\n /** Callback when primary succeeds */\n onSuccess: functionSchema.optional(),\n /** Logger instance */\n logger: loggerSchema,\n});\n\nexport type FallbackConfig = z.infer<typeof fallbackConfigSchema>;\n","import { z } from 'zod';\n\n/** Maximum reasonable token count (1 billion) */\nconst MAX_TOKENS = 1_000_000_000;\n\n/** Maximum reasonable timestamp (year 2100 in ms) */\nconst MAX_TIMESTAMP = 4_102_444_800_000;\n\n/**\n * Zod schema for validating bucket state from external storage.\n * Use this to validate data retrieved from untrusted storage sources.\n * Includes bounds checking to prevent malicious or corrupted data.\n */\nexport const bucketStateSchema = z.object({\n tokens: z.number().nonnegative().max(MAX_TOKENS),\n lastRefill: z.number().int().nonnegative().max(MAX_TIMESTAMP),\n});\n\n/**\n * State of a rate limit bucket for external storage.\n * Contains all data needed to reconstruct bucket state across invocations.\n */\nexport interface BucketState {\n /** Current number of tokens in the bucket */\n readonly tokens: number;\n /** Timestamp (ms since epoch) of last token refill */\n readonly lastRefill: number;\n}\n\n/**\n * Validates bucket state from external storage.\n * Returns the validated state or null if invalid.\n *\n * @param data - Raw data from storage\n * @returns Validated BucketState or null if invalid\n *\n * @example\n * ```typescript\n * const data = await redis.get(key);\n * const state = validateBucketState(JSON.parse(data));\n * if (state === null) {\n * // Invalid data, treat as new bucket\n * }\n * ```\n */\nexport function validateBucketState(data: unknown): BucketState | null {\n const result = bucketStateSchema.safeParse(data);\n return result.success ? result.data : null;\n}\n\n/**\n * Result of a compare-and-set operation.\n */\nexport interface CompareAndSetResult {\n /** Whether the update was successful */\n readonly success: boolean;\n /** The current state (after operation) */\n readonly currentState: BucketState | null;\n}\n\n/** Maximum key length after sanitization */\nconst MAX_KEY_LENGTH = 256;\n\n/** Pre-compiled regex for control characters (for performance) */\n// eslint-disable-next-line no-control-regex\nconst CONTROL_CHARS_REGEX = /[\\x00-\\x1f\\x7f]/g;\n\n/** Pre-compiled regex for path separators (for performance) */\nconst PATH_SEPARATORS_REGEX = /[/\\\\]/g;\n\n/**\n * Sanitizes a storage key to prevent injection attacks.\n * Removes or encodes potentially dangerous characters.\n *\n * Optimization: Truncates before regex processing to limit work on long strings.\n *\n * @param key - Raw key from user input\n * @returns Sanitized key safe for storage operations\n *\n * @example\n * ```typescript\n * const safeKey = sanitizeStorageKey(userProvidedKey);\n * await storage.get(safeKey);\n * ```\n */\nexport function sanitizeStorageKey(key: string): string {\n // Truncate first to limit regex work on long strings\n const truncated = key.length > MAX_KEY_LENGTH ? key.slice(0, MAX_KEY_LENGTH) : key;\n\n // Replace control characters, null bytes, and path separators\n // Keep alphanumeric, dash, underscore, dot, colon, at\n return truncated\n .replace(CONTROL_CHARS_REGEX, '') // Remove control characters\n .replace(PATH_SEPARATORS_REGEX, '_'); // Replace path separators\n}\n\n/**\n * Storage adapter interface for rate limiting.\n *\n * Implement this interface to persist rate limit state across serverless\n * invocations or distributed systems. The storage adapter enables rate\n * limiting in environments where in-memory state doesn't persist.\n *\n * ## Concurrency Warning\n *\n * **Important:** The basic get/set interface has inherent TOCTOU (time-of-check\n * to time-of-use) race conditions in distributed systems. Between reading the\n * bucket state and writing the updated state, another process may have modified\n * the bucket. This can result in rate limits being slightly over or under the\n * configured limit.\n *\n * For precise rate limiting in high-concurrency distributed environments,\n * implement the optional `compareAndSet` method with atomic operations:\n *\n * - **Redis**: Use Lua scripts with WATCH/MULTI/EXEC\n * - **DynamoDB**: Use conditional writes with version checks\n * - **Forge Storage**: Accepts eventual consistency\n *\n * For most use cases, the slight inaccuracy from race conditions is acceptable.\n * The rate limiter still provides effective protection against abuse.\n *\n * @example Redis implementation with atomic operations\n * ```typescript\n * const redisStorage: RateLimitStorage = {\n * async get(key) {\n * const data = await redis.get(`ratelimit:${key}`);\n * if (!data) return null;\n * return validateBucketState(JSON.parse(data));\n * },\n * async set(key, state, ttlMs) {\n * const value = JSON.stringify(state);\n * if (ttlMs) {\n * await redis.set(`ratelimit:${key}`, value, 'PX', ttlMs);\n * } else {\n * await redis.set(`ratelimit:${key}`, value);\n * }\n * },\n * async compareAndSet(key, expected, newState, ttlMs) {\n * // Use Lua script for atomic compare-and-set\n * const script = `\n * local current = redis.call('GET', KEYS[1])\n * if current == ARGV[1] or (current == false and ARGV[1] == 'null') then\n * if ARGV[3] then\n * redis.call('SET', KEYS[1], ARGV[2], 'PX', ARGV[3])\n * else\n * redis.call('SET', KEYS[1], ARGV[2])\n * end\n * return {1, ARGV[2]}\n * end\n * return {0, current or 'null'}\n * `;\n * const expectedStr = expected ? JSON.stringify(expected) : 'null';\n * const [success, currentStr] = await redis.eval(script, 1,\n * `ratelimit:${key}`, expectedStr, JSON.stringify(newState), ttlMs?.toString()\n * );\n * return {\n * success: success === 1,\n * currentState: currentStr === 'null' ? null : validateBucketState(JSON.parse(currentStr))\n * };\n * },\n * async delete(key) {\n * await redis.del(`ratelimit:${key}`);\n * },\n * async clear() {\n * const keys = await redis.keys('ratelimit:*');\n * if (keys.length > 0) {\n * await redis.del(...keys);\n * }\n * }\n * };\n * ```\n *\n * @example Forge Storage implementation (accepts eventual consistency)\n * ```typescript\n * import { storage } from '@forge/api';\n *\n * const forgeStorage: RateLimitStorage = {\n * async get(key) {\n * const data = await storage.get(`ratelimit:${key}`);\n * return validateBucketState(data);\n * },\n * async set(key, state) {\n * await storage.set(`ratelimit:${key}`, state);\n * },\n * async delete(key) {\n * await storage.delete(`ratelimit:${key}`);\n * }\n * };\n * ```\n */\nexport interface RateLimitStorage {\n /**\n * Retrieve bucket state for a key.\n *\n * @param key - The rate limiting key (already sanitized)\n * @returns The bucket state, or null if not found\n */\n get(key: string): Promise<BucketState | null>;\n\n /**\n * Store bucket state for a key.\n *\n * @param key - The rate limiting key (already sanitized)\n * @param state - The bucket state to store\n * @param ttlMs - Optional TTL in milliseconds for automatic cleanup.\n * Recommended: set to interval * (burst / rate) * 2 to auto-expire\n * stale buckets while keeping active ones alive.\n */\n set(key: string, state: BucketState, ttlMs?: number): Promise<void>;\n\n /**\n * Atomically compare and set bucket state.\n * Optional - implement for precise rate limiting in distributed systems.\n *\n * This method should atomically:\n * 1. Check if the current state matches `expected`\n * 2. If it matches, update to `newState` and return success\n * 3. If it doesn't match, return failure with the current state\n *\n * @param key - The rate limiting key (already sanitized)\n * @param expected - The expected current state (null if expecting no entry)\n * @param newState - The new state to set if expected matches\n * @param ttlMs - Optional TTL in milliseconds\n * @returns Result indicating success and current state\n */\n compareAndSet?(\n key: string,\n expected: BucketState | null,\n newState: BucketState,\n ttlMs?: number\n ): Promise<CompareAndSetResult>;\n\n /**\n * Delete bucket state for a key.\n * Optional - if not implemented, reset operations on individual keys\n * will not be supported.\n *\n * @param key - The rate limiting key (already sanitized)\n */\n delete?(key: string): Promise<void>;\n\n /**\n * Clear all bucket states.\n * Optional - if not implemented, full reset operations will not clear storage.\n */\n clear?(): Promise<void>;\n}\n\n/**\n * In-memory storage implementation using Map.\n * This is the default storage used when no external storage is provided.\n *\n * Features:\n * - LRU eviction when maxEntries is exceeded\n * - Synchronous operations (async interface for compatibility)\n * - No persistence across process restarts\n * - No race conditions (single-threaded JavaScript)\n *\n * @example\n * ```typescript\n * const storage = new MemoryStorage({ maxEntries: 10000 });\n * const limiter = new RateLimiter({ rate: 100, storage });\n * ```\n */\nexport class MemoryStorage implements RateLimitStorage {\n /** Cached resolved promise for void returns to avoid allocation */\n private static readonly RESOLVED_VOID = Promise.resolve();\n\n /** Cached resolved promise for null returns to avoid allocation */\n private static readonly RESOLVED_NULL: Promise<BucketState | null> = Promise.resolve(null);\n\n private readonly entries = new Map<string, BucketState>();\n private readonly maxEntries: number;\n private evictionCount = 0;\n\n /**\n * Create a new in-memory storage.\n *\n * @param options - Storage options\n * @param options.maxEntries - Maximum entries before LRU eviction (0 = unlimited, default: 10000)\n */\n constructor(options?: { readonly maxEntries?: number }) {\n this.maxEntries = options?.maxEntries ?? 10000;\n }\n\n get(key: string): Promise<BucketState | null> {\n const state = this.getSync(key);\n return state === null ? MemoryStorage.RESOLVED_NULL : Promise.resolve(state);\n }\n\n set(key: string, state: BucketState): Promise<void> {\n this.setSync(key, state);\n return MemoryStorage.RESOLVED_VOID;\n }\n\n compareAndSet(\n key: string,\n expected: BucketState | null,\n newState: BucketState\n ): Promise<CompareAndSetResult> {\n const current = this.entries.get(key) ?? null;\n\n // Check if current matches expected\n const matches =\n current === expected ||\n (current !== null &&\n expected !== null &&\n current.tokens === expected.tokens &&\n current.lastRefill === expected.lastRefill);\n\n if (matches) {\n this.setSync(key, newState);\n return Promise.resolve({ success: true, currentState: newState });\n }\n\n return Promise.resolve({ success: false, currentState: current });\n }\n\n delete(key: string): Promise<void> {\n this.deleteSync(key);\n return MemoryStorage.RESOLVED_VOID;\n }\n\n clear(): Promise<void> {\n this.clearSync();\n return MemoryStorage.RESOLVED_VOID;\n }\n\n /**\n * Get the current number of entries.\n */\n size(): number {\n return this.entries.size;\n }\n\n /**\n * Get the total number of evictions since creation.\n */\n getEvictionCount(): number {\n return this.evictionCount;\n }\n\n /**\n * Synchronous get for backward compatibility with sync rate limiter methods.\n * @internal\n */\n getSync(key: string): BucketState | null {\n const state = this.entries.get(key);\n if (state) {\n // LRU touch: move to end\n this.entries.delete(key);\n this.entries.set(key, state);\n }\n return state ?? null;\n }\n\n /**\n * Synchronous set for backward compatibility with sync rate limiter methods.\n * @internal\n */\n setSync(key: string, state: BucketState): void {\n if (this.entries.has(key)) {\n this.entries.delete(key);\n } else if (this.maxEntries > 0 && this.entries.size >= this.maxEntries) {\n const firstKey = this.entries.keys().next().value;\n if (firstKey !== undefined) {\n this.entries.delete(firstKey);\n this.evictionCount++;\n }\n }\n this.entries.set(key, state);\n }\n\n /**\n * Synchronous delete for backward compatibility.\n * @internal\n */\n deleteSync(key: string): void {\n this.entries.delete(key);\n }\n\n /**\n * Synchronous clear for backward compatibility.\n * @internal\n */\n clearSync(): void {\n this.entries.clear();\n }\n}\n"]} | ||
| {"version":3,"sources":["../src/errors.ts","../src/types.ts","../src/utils.ts","../src/schemas.ts","../src/storage.ts"],"names":["z"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Base error class for all Fortify errors.\n */\nexport class FortifyError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'FortifyError';\n // Maintains proper stack trace for where our error was thrown (only available on V8)\n // Use typeof check to avoid unnecessary-condition lint error\n if (typeof Error.captureStackTrace === 'function') {\n Error.captureStackTrace(this, this.constructor);\n }\n }\n\n /**\n * Serialize to JSON for structured logging.\n * @returns JSON-safe representation of the error\n */\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n message: this.message,\n };\n }\n}\n\n/**\n * Error thrown when circuit breaker is in open state and rejecting requests.\n */\nexport class CircuitOpenError extends FortifyError {\n constructor(message = 'Circuit breaker is open') {\n super(message);\n this.name = 'CircuitOpenError';\n }\n}\n\n/**\n * Error thrown when rate limit is exceeded.\n *\n * **Security Note:** The `key` property contains the rate limiting key\n * (e.g., user ID, IP address). When logging or exposing errors to users,\n * consider whether exposing this value is appropriate for your use case.\n * For sensitive keys, you may want to redact or omit this property when\n * serializing errors for external consumption.\n *\n * The `toJSON()` method excludes the key by default for security.\n * Use `toJSON(true)` to include the key when needed.\n */\nexport class RateLimitExceededError extends FortifyError {\n public readonly key: string | undefined;\n\n constructor(message = 'Rate limit exceeded', key?: string) {\n super(message);\n this.name = 'RateLimitExceededError';\n this.key = key;\n }\n\n /**\n * Serialize to JSON, optionally including the key.\n *\n * @param includeKey - Whether to include the rate limit key (default: false for security)\n * @returns JSON-safe representation of the error\n */\n override toJSON(includeKey = false): Record<string, unknown> {\n const result: Record<string, unknown> = {\n name: this.name,\n message: this.message,\n };\n if (includeKey && this.key !== undefined) {\n result.key = this.key;\n }\n return result;\n }\n}\n\n/**\n * Error thrown when bulkhead is at capacity.\n */\nexport class BulkheadFullError extends FortifyError {\n public readonly activeCount: number;\n public readonly queuedCount: number;\n\n constructor(\n message = 'Bulkhead is full',\n activeCount = 0,\n queuedCount = 0\n ) {\n super(message);\n this.name = 'BulkheadFullError';\n this.activeCount = activeCount;\n this.queuedCount = queuedCount;\n }\n\n override toJSON(): Record<string, unknown> {\n return {\n ...super.toJSON(),\n activeCount: this.activeCount,\n queuedCount: this.queuedCount,\n };\n }\n}\n\n/**\n * Error thrown when operation times out.\n */\nexport class TimeoutError extends FortifyError {\n public readonly timeoutMs: number;\n\n constructor(message = 'Operation timed out', timeoutMs = 0) {\n super(message);\n this.name = 'TimeoutError';\n this.timeoutMs = timeoutMs;\n }\n\n /**\n * Alias for timeoutMs for convenience.\n */\n get duration(): number {\n return this.timeoutMs;\n }\n\n override toJSON(): Record<string, unknown> {\n return {\n ...super.toJSON(),\n timeoutMs: this.timeoutMs,\n };\n }\n}\n\n/**\n * Error thrown when maximum retry attempts are reached.\n */\nexport class MaxAttemptsReachedError extends FortifyError {\n public readonly attempts: number;\n public readonly lastError: Error | undefined;\n\n constructor(\n message = 'Maximum retry attempts reached',\n attempts = 0,\n lastError?: Error\n ) {\n super(message);\n this.name = 'MaxAttemptsReachedError';\n this.attempts = attempts;\n this.lastError = lastError;\n }\n\n override toJSON(): Record<string, unknown> {\n return {\n ...super.toJSON(),\n attempts: this.attempts,\n lastError: this.lastError\n ? { name: this.lastError.name, message: this.lastError.message }\n : undefined,\n };\n }\n}\n\n/**\n * Error thrown when bulkhead is closed and no new operations accepted.\n */\nexport class BulkheadClosedError extends FortifyError {\n constructor(message = 'Bulkhead is closed') {\n super(message);\n this.name = 'BulkheadClosedError';\n }\n}\n\n/**\n * Interface for errors that can indicate whether they are retryable.\n */\nexport interface RetryableError {\n retryable: boolean;\n}\n\n/**\n * Type guard to check if an error implements RetryableError interface.\n */\nexport function isRetryableError(error: unknown): error is Error & RetryableError {\n return (\n error instanceof Error &&\n 'retryable' in error &&\n typeof (error as RetryableError).retryable === 'boolean'\n );\n}\n\n/**\n * Wrapper class to mark an error as retryable.\n */\nexport class RetryableErrorWrapper extends Error implements RetryableError {\n public readonly retryable: boolean;\n public override readonly cause: Error;\n\n constructor(error: Error, retryable: boolean) {\n super(error.message, { cause: error });\n this.name = 'RetryableErrorWrapper';\n this.retryable = retryable;\n this.cause = error;\n if (error.stack) {\n this.stack = error.stack;\n }\n }\n}\n\n/**\n * Wrap an error as retryable.\n */\nexport function asRetryable(error: Error, retryable = true): RetryableErrorWrapper {\n return new RetryableErrorWrapper(error, retryable);\n}\n\n/**\n * Wrap an error as non-retryable.\n */\nexport function asNonRetryable(error: Error): RetryableErrorWrapper {\n return new RetryableErrorWrapper(error, false);\n}\n\n/**\n * Check if an error is retryable.\n * Returns true if the error implements RetryableError and retryable is true.\n * Returns undefined if the error doesn't implement RetryableError (let caller decide).\n */\nexport function isRetryable(error: unknown): boolean | undefined {\n if (isRetryableError(error)) {\n return error.retryable;\n }\n return undefined;\n}\n","/**\n * Represents an async operation that can be executed with cancellation support.\n * The signal parameter allows the operation to be cancelled via AbortController.\n *\n * @template T - The return type of the operation\n */\nexport type Operation<T> = (signal: AbortSignal) => Promise<T>;\n\n/**\n * Callback for state changes in stateful patterns (e.g., circuit breaker).\n *\n * @template S - The state type\n */\nexport type StateChangeCallback<S> = (from: S, to: S) => void;\n\n/**\n * Callback for error events.\n */\nexport type ErrorCallback = (error: Error) => void;\n\n/**\n * Simple void callback.\n */\nexport type VoidCallback = () => void;\n\n/**\n * Callback for retry events.\n */\nexport type RetryCallback = (attempt: number, error: Error) => void;\n\n/**\n * Callback for rate limit events.\n */\nexport type RateLimitCallback = (key: string) => void;\n\n/**\n * Logger interface for structured logging across all patterns.\n * Compatible with pino, winston, console, and custom implementations.\n */\nexport interface FortifyLogger {\n debug(msg: string, context?: Record<string, unknown>): void;\n info(msg: string, context?: Record<string, unknown>): void;\n warn(msg: string, context?: Record<string, unknown>): void;\n error(msg: string, context?: Record<string, unknown>): void;\n}\n\n/**\n * No-operation logger that discards all log messages.\n * Useful for testing or when logging is not needed.\n */\nexport const noopLogger: FortifyLogger = {\n debug: () => undefined,\n info: () => undefined,\n warn: () => undefined,\n error: () => undefined,\n};\n\n/**\n * Console-based logger implementation.\n * Browser-friendly and works in all environments.\n */\nexport const consoleLogger: FortifyLogger = {\n debug: (msg, context) => {\n if (context) {\n console.debug(`[fortify] ${msg}`, context);\n } else {\n console.debug(`[fortify] ${msg}`);\n }\n },\n info: (msg, context) => {\n if (context) {\n console.info(`[fortify] ${msg}`, context);\n } else {\n console.info(`[fortify] ${msg}`);\n }\n },\n warn: (msg, context) => {\n if (context) {\n console.warn(`[fortify] ${msg}`, context);\n } else {\n console.warn(`[fortify] ${msg}`);\n }\n },\n error: (msg, context) => {\n if (context) {\n console.error(`[fortify] ${msg}`, context);\n } else {\n console.error(`[fortify] ${msg}`);\n }\n },\n};\n\n/**\n * Generic pattern interface that all resilience patterns implement.\n *\n * @template T - The return type of the operation\n */\nexport interface Pattern<T> {\n /**\n * Execute an operation with the pattern's resilience logic.\n *\n * @param operation - The async operation to execute\n * @param signal - Optional AbortSignal for cancellation\n * @returns Promise resolving to the operation result\n */\n execute(operation: Operation<T>, signal?: AbortSignal): Promise<T>;\n}\n\n/**\n * Interface for patterns that support closing/cleanup.\n */\nexport interface Closeable {\n /**\n * Close the pattern and release resources.\n * May wait for in-flight operations to complete.\n */\n close(): Promise<void>;\n}\n\n/**\n * Interface for patterns that support resetting state.\n */\nexport interface Resettable {\n /**\n * Reset the pattern to its initial state.\n */\n reset(): void;\n}\n","import { TimeoutError } from './errors.js';\n\n/**\n * A static never-aborted AbortSignal for use when no signal is provided.\n * Avoids allocating a new AbortController on every operation call.\n *\n * This is a performance optimization for hot paths where signal is optional\n * but the operation signature requires AbortSignal.\n */\nexport const NEVER_ABORTED_SIGNAL: AbortSignal = new AbortController().signal;\n\n/**\n * Sleep for a specified duration with optional cancellation support.\n *\n * @param ms - Duration in milliseconds\n * @param signal - Optional AbortSignal for cancellation\n * @returns Promise that resolves after the delay or rejects if cancelled\n */\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(ensureError(signal.reason));\n return;\n }\n\n const timeoutId = setTimeout(resolve, ms);\n\n signal?.addEventListener(\n 'abort',\n () => {\n clearTimeout(timeoutId);\n reject(ensureError(signal.reason));\n },\n { once: true }\n );\n });\n}\n\n/**\n * Ensure a value is an Error instance.\n * @param reason - The reason to convert to an Error\n * @returns An Error instance\n */\nfunction ensureError(reason: unknown): Error {\n if (reason instanceof Error) {\n return reason;\n }\n if (typeof reason === 'string') {\n return new Error(reason);\n }\n if (reason !== undefined && reason !== null) {\n // Handle objects and other types safely\n try {\n return new Error(JSON.stringify(reason));\n } catch {\n return new Error('Unknown error');\n }\n }\n return new DOMException('Aborted', 'AbortError');\n}\n\n/**\n * Wrap a promise with a timeout.\n *\n * @template T - The promise return type\n * @param promise - The promise to wrap\n * @param ms - Timeout duration in milliseconds\n * @param signal - Optional external AbortSignal for cancellation\n * @returns Promise that resolves with the result or rejects with TimeoutError\n */\nexport async function withTimeout<T>(\n promise: Promise<T>,\n ms: number,\n signal?: AbortSignal\n): Promise<T> {\n // Check if already aborted\n if (signal?.aborted) {\n throw ensureError(signal.reason);\n }\n\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n reject(new TimeoutError(`Operation timed out after ${String(ms)}ms`, ms));\n }, ms);\n });\n\n // Handle external signal\n const abortPromise = signal\n ? new Promise<never>((_, reject) => {\n signal.addEventListener(\n 'abort',\n () => reject(ensureError(signal.reason)),\n { once: true }\n );\n })\n : null;\n\n try {\n const racers: Promise<T>[] = [promise, timeoutPromise];\n if (abortPromise) {\n racers.push(abortPromise);\n }\n return await Promise.race(racers);\n } finally {\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n }\n }\n}\n\n/**\n * Execute an operation with a timeout, passing the combined signal to the operation.\n *\n * @template T - The operation return type\n * @param operation - The operation to execute\n * @param ms - Timeout duration in milliseconds\n * @param signal - Optional external AbortSignal for cancellation\n * @returns Promise that resolves with the result or rejects with TimeoutError\n */\nexport async function executeWithTimeout<T>(\n operation: (signal: AbortSignal) => Promise<T>,\n ms: number,\n signal?: AbortSignal\n): Promise<T> {\n const controller = new AbortController();\n\n // Combine signals\n const combinedSignal = combineSignals(signal, controller.signal);\n\n const timeoutId = setTimeout(() => {\n controller.abort(new TimeoutError(`Operation timed out after ${String(ms)}ms`, ms));\n }, ms);\n\n try {\n return await operation(combinedSignal);\n } finally {\n clearTimeout(timeoutId);\n }\n}\n\n/**\n * Combine multiple AbortSignals into one.\n * Returns a signal that aborts when any of the input signals abort.\n *\n * Uses AbortSignal.any() when available (Node 20+, modern browsers).\n * Falls back to a manual implementation that properly cleans up event listeners.\n *\n * @param signals - AbortSignals to combine (undefined values are filtered out)\n * @returns Combined AbortSignal\n */\nexport function combineSignals(...signals: (AbortSignal | undefined)[]): AbortSignal {\n const validSignals = signals.filter((s): s is AbortSignal => s !== undefined);\n\n if (validSignals.length === 0) {\n return NEVER_ABORTED_SIGNAL;\n }\n\n const firstSignal = validSignals[0];\n if (validSignals.length === 1 && firstSignal) {\n return firstSignal;\n }\n\n // Use AbortSignal.any if available (Node 20+, modern browsers)\n if ('any' in AbortSignal) {\n return AbortSignal.any(validSignals);\n }\n\n // Fallback: create a new controller and link it to all signals\n // Track listeners so we can clean them up to prevent memory leaks\n const controller = new AbortController();\n const listeners: { signal: AbortSignal; listener: () => void }[] = [];\n\n // Cleanup function to remove all listeners\n const cleanup = () => {\n for (const { signal, listener } of listeners) {\n signal.removeEventListener('abort', listener);\n }\n listeners.length = 0;\n };\n\n for (const signal of validSignals) {\n if (signal.aborted) {\n controller.abort(signal.reason);\n return controller.signal;\n }\n\n const listener = () => {\n cleanup(); // Clean up all listeners when any signal aborts\n controller.abort(signal.reason);\n };\n\n listeners.push({ signal, listener });\n signal.addEventListener('abort', listener, { once: true });\n }\n\n return controller.signal;\n}\n\n/**\n * Check if an error is an abort error (from AbortController).\n *\n * @param error - Error to check\n * @returns True if the error is an AbortError\n */\nexport function isAbortError(error: unknown): boolean {\n return (\n error instanceof DOMException &&\n error.name === 'AbortError'\n );\n}\n\n/**\n * Throw if the given signal is aborted.\n * Useful for checking signal state early in operations to avoid unnecessary work.\n *\n * @param signal - AbortSignal to check\n * @throws {DOMException} If signal is aborted (AbortError)\n */\nexport function throwIfAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted) {\n throw signal.reason ?? new DOMException('Aborted', 'AbortError');\n }\n}\n\n/**\n * Safely execute a callback, catching and logging any errors.\n * Used for user-provided callbacks to prevent them from breaking the pattern.\n *\n * @param callback - Callback to execute\n * @param onError - Optional error handler\n */\nexport function safeCallback<T extends (...args: unknown[]) => unknown>(\n callback: T | undefined,\n onError?: (error: Error) => void\n): T | undefined {\n if (!callback) return undefined;\n\n return ((...args: Parameters<T>): ReturnType<T> | undefined => {\n try {\n return callback(...args) as ReturnType<T>;\n } catch (error) {\n if (onError && error instanceof Error) {\n onError(error);\n }\n return undefined;\n }\n }) as T;\n}\n\n/**\n * Jitter mode for delay randomization.\n *\n * - `full`: Random delay from 0 to base delay (aggressive, best for thundering herd)\n * - `equal`: Random delay from 50% to 100% of base delay (balanced)\n * - `decorrelated`: Each sleep is random between base and previous sleep * 3 (AWS-style)\n */\nexport type JitterMode = 'full' | 'equal' | 'decorrelated';\n\n/**\n * Calculate jittered delay to prevent thundering herd.\n *\n * Uses \"equal jitter\" by default: random value between 50-100% of the delay.\n * This provides good dispersion while maintaining reasonable minimum delays.\n *\n * For more aggressive jitter, use mode='full' (0-100% of delay).\n * For AWS-style decorrelated jitter, use mode='decorrelated' with previousDelay.\n *\n * @param delay - Base delay in milliseconds\n * @param mode - Jitter mode: 'full', 'equal', or 'decorrelated' (default: 'equal')\n * @param previousDelay - Previous delay for decorrelated mode (default: delay)\n * @returns Jittered delay\n *\n * @example\n * ```typescript\n * // Equal jitter (default): 50-100% of delay\n * addJitter(1000); // Returns 500-1000ms\n *\n * // Full jitter: 0-100% of delay\n * addJitter(1000, 'full'); // Returns 0-1000ms\n *\n * // Decorrelated jitter (AWS-style)\n * let sleep = initialDelay;\n * sleep = addJitter(baseDelay, 'decorrelated', sleep);\n * ```\n */\nexport function addJitter(\n delay: number,\n mode: JitterMode = 'equal',\n previousDelay?: number\n): number {\n switch (mode) {\n case 'full':\n // Full jitter: random(0, delay)\n return Math.floor(Math.random() * delay);\n\n case 'decorrelated': {\n // Decorrelated jitter: random(base, previous * 3)\n // Capped at delay to prevent unbounded growth\n const prev = previousDelay ?? delay;\n const min = delay;\n const max = Math.min(prev * 3, delay * 10); // Cap at 10x base\n return Math.floor(min + Math.random() * (max - min));\n }\n\n case 'equal':\n default:\n // Equal jitter: delay/2 + random(0, delay/2)\n // Results in 50-100% of original delay\n return Math.floor(delay * 0.5 + Math.random() * delay * 0.5);\n }\n}\n\n/**\n * Clamp a number between min and max values.\n *\n * @param value - Value to clamp\n * @param min - Minimum value\n * @param max - Maximum value\n * @returns Clamped value\n */\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n\n/**\n * Get current timestamp in milliseconds.\n * Uses performance.now() if available for higher precision.\n *\n * @returns Current timestamp in milliseconds\n */\nexport function now(): number {\n if (typeof performance !== 'undefined' && typeof performance.now === 'function') {\n return performance.now();\n }\n return Date.now();\n}\n","import { z } from 'zod';\n\n/**\n * Schema for a function type (Zod 4 compatible).\n * Uses z.function() which validates that the value is a function.\n */\nconst functionSchema = z.function();\n\n/**\n * Base schema for logger configuration.\n * Validates that the logger has the required methods.\n */\nexport const loggerSchema = z.object({\n debug: functionSchema,\n info: functionSchema,\n warn: functionSchema,\n error: functionSchema,\n}).optional();\n\n/**\n * Schema for timeout configuration.\n */\nexport const timeoutConfigSchema = z.object({\n /** Default timeout in milliseconds (default: 30000) */\n defaultTimeout: z.number().int().positive().default(30000),\n /** Callback when timeout occurs */\n onTimeout: functionSchema.optional(),\n /** Logger instance */\n logger: loggerSchema,\n});\n\nexport type TimeoutConfig = z.infer<typeof timeoutConfigSchema>;\n\n/**\n * Schema for retry backoff policy.\n */\nexport const backoffPolicySchema = z.enum(['exponential', 'linear', 'constant']);\n\nexport type BackoffPolicy = z.infer<typeof backoffPolicySchema>;\n\n/**\n * Schema for retry configuration.\n */\nexport const retryConfigSchema = z.object({\n /** Maximum number of attempts including the first (default: 3) */\n maxAttempts: z.number().int().positive().default(3),\n /** Initial delay before first retry in milliseconds (default: 100) */\n initialDelay: z.number().int().positive().default(100),\n /** Maximum delay between retries in milliseconds */\n maxDelay: z.number().int().positive().optional(),\n /** Backoff strategy (default: 'exponential') */\n backoffPolicy: backoffPolicySchema.default('exponential'),\n /** Multiplier for exponential backoff (default: 2.0) */\n multiplier: z.number().positive().default(2.0),\n /** Add random jitter to delays (default: false) */\n jitter: z.boolean().default(false),\n /** Custom function to determine if error is retryable */\n isRetryable: functionSchema.optional(),\n /** Callback on each retry attempt */\n onRetry: functionSchema.optional(),\n /** Logger instance */\n logger: loggerSchema,\n});\n\nexport type RetryConfig = z.infer<typeof retryConfigSchema>;\n\n/**\n * Schema for circuit breaker state.\n */\nexport const circuitBreakerStateSchema = z.enum(['closed', 'open', 'half-open']);\n\nexport type CircuitBreakerState = z.infer<typeof circuitBreakerStateSchema>;\n\n/**\n * Schema for circuit breaker counts/metrics.\n */\nexport const circuitBreakerCountsSchema = z.object({\n requests: z.number().int().nonnegative(),\n totalSuccesses: z.number().int().nonnegative(),\n totalFailures: z.number().int().nonnegative(),\n consecutiveSuccesses: z.number().int().nonnegative(),\n consecutiveFailures: z.number().int().nonnegative(),\n});\n\nexport type CircuitBreakerCounts = z.infer<typeof circuitBreakerCountsSchema>;\n\n/**\n * Schema for circuit breaker configuration.\n */\nexport const circuitBreakerConfigSchema = z.object({\n /** Maximum consecutive failures before opening (default: 5) */\n maxFailures: z.number().int().positive().default(5),\n /** Duration in open state before transitioning to half-open in milliseconds (default: 60000) */\n timeout: z.number().int().positive().default(60000),\n /** Maximum requests allowed in half-open state (default: 1) */\n halfOpenMaxRequests: z.number().int().positive().default(1),\n /** Period to clear counts when closed, 0 means never (default: 0) */\n interval: z.number().int().nonnegative().default(0),\n /** Custom function to determine when to trip the breaker */\n readyToTrip: functionSchema.optional(),\n /** Custom function to determine if result is successful */\n isSuccessful: functionSchema.optional(),\n /** Callback on state change */\n onStateChange: functionSchema.optional(),\n /** Logger instance */\n logger: loggerSchema,\n});\n\nexport type CircuitBreakerConfig = z.infer<typeof circuitBreakerConfigSchema>;\n\n/**\n * Schema for rate limiter configuration.\n */\nexport const rateLimitConfigSchema = z.object({\n /** Number of tokens added per interval (default: 100) */\n rate: z.number().int().positive().default(100),\n /** Maximum bucket capacity (defaults to rate) */\n burst: z.number().int().positive().optional(),\n /** Interval for token refill in milliseconds (default: 1000) */\n interval: z.number().int().positive().default(1000),\n /** Callback when rate limit is hit */\n onLimit: functionSchema.optional(),\n /** Logger instance */\n logger: loggerSchema,\n});\n\nexport type RateLimitConfig = z.infer<typeof rateLimitConfigSchema>;\n\n/**\n * Schema for bulkhead configuration.\n */\nexport const bulkheadConfigSchema = z.object({\n /** Maximum concurrent executions (default: 10) */\n maxConcurrent: z.number().int().positive().default(10),\n /** Maximum queued requests, 0 means no queueing (default: 0) */\n maxQueue: z.number().int().nonnegative().default(0),\n /** Maximum time to wait in queue in milliseconds */\n queueTimeout: z.number().int().positive().optional(),\n /** Callback when request is rejected */\n onRejected: functionSchema.optional(),\n /** Logger instance */\n logger: loggerSchema,\n});\n\nexport type BulkheadConfig = z.infer<typeof bulkheadConfigSchema>;\n\n/**\n * Schema for fallback configuration.\n */\nexport const fallbackConfigSchema = z.object({\n /** Fallback function to execute when primary fails */\n fallback: functionSchema,\n /** Custom function to determine if fallback should be used */\n shouldFallback: functionSchema.optional(),\n /** Callback when fallback is triggered */\n onFallback: functionSchema.optional(),\n /** Callback when primary succeeds */\n onSuccess: functionSchema.optional(),\n /** Logger instance */\n logger: loggerSchema,\n});\n\nexport type FallbackConfig = z.infer<typeof fallbackConfigSchema>;\n","import { z } from 'zod';\n\n/** Maximum reasonable token count (1 billion) */\nconst MAX_TOKENS = 1_000_000_000;\n\n/** Maximum reasonable timestamp (year 2100 in ms) */\nconst MAX_TIMESTAMP = 4_102_444_800_000;\n\n/**\n * Zod schema for validating bucket state from external storage.\n * Use this to validate data retrieved from untrusted storage sources.\n * Includes bounds checking to prevent malicious or corrupted data.\n */\nexport const bucketStateSchema = z.object({\n tokens: z.number().nonnegative().max(MAX_TOKENS),\n lastRefill: z.number().int().nonnegative().max(MAX_TIMESTAMP),\n});\n\n/**\n * State of a rate limit bucket for external storage.\n * Contains all data needed to reconstruct bucket state across invocations.\n */\nexport interface BucketState {\n /** Current number of tokens in the bucket */\n readonly tokens: number;\n /** Timestamp (ms since epoch) of last token refill */\n readonly lastRefill: number;\n}\n\n/**\n * Validates bucket state from external storage.\n * Returns the validated state or null if invalid.\n *\n * @param data - Raw data from storage\n * @returns Validated BucketState or null if invalid\n *\n * @example\n * ```typescript\n * const data = await redis.get(key);\n * const state = validateBucketState(JSON.parse(data));\n * if (state === null) {\n * // Invalid data, treat as new bucket\n * }\n * ```\n */\nexport function validateBucketState(data: unknown): BucketState | null {\n const result = bucketStateSchema.safeParse(data);\n return result.success ? result.data : null;\n}\n\n/**\n * Result of a compare-and-set operation.\n */\nexport interface CompareAndSetResult {\n /** Whether the update was successful */\n readonly success: boolean;\n /** The current state (after operation) */\n readonly currentState: BucketState | null;\n}\n\n/** Maximum key length after sanitization */\nconst MAX_KEY_LENGTH = 256;\n\n/** Pre-compiled regex for control characters (for performance) */\n// eslint-disable-next-line no-control-regex\nconst CONTROL_CHARS_REGEX = /[\\x00-\\x1f\\x7f]/g;\n\n/** Pre-compiled regex for path separators (for performance) */\nconst PATH_SEPARATORS_REGEX = /[/\\\\]/g;\n\n/**\n * Sanitizes a storage key to prevent injection attacks.\n * Removes or encodes potentially dangerous characters.\n *\n * Optimization: Truncates before regex processing to limit work on long strings.\n *\n * @param key - Raw key from user input\n * @returns Sanitized key safe for storage operations\n *\n * @example\n * ```typescript\n * const safeKey = sanitizeStorageKey(userProvidedKey);\n * await storage.get(safeKey);\n * ```\n */\nexport function sanitizeStorageKey(key: string): string {\n // Truncate first to limit regex work on long strings\n const truncated = key.length > MAX_KEY_LENGTH ? key.slice(0, MAX_KEY_LENGTH) : key;\n\n // Replace control characters, null bytes, and path separators\n // Keep alphanumeric, dash, underscore, dot, colon, at\n return truncated\n .replace(CONTROL_CHARS_REGEX, '') // Remove control characters\n .replace(PATH_SEPARATORS_REGEX, '_'); // Replace path separators\n}\n\n/**\n * Storage adapter interface for rate limiting.\n *\n * Implement this interface to persist rate limit state across serverless\n * invocations or distributed systems. The storage adapter enables rate\n * limiting in environments where in-memory state doesn't persist.\n *\n * ## Concurrency Warning\n *\n * **Important:** The basic get/set interface has inherent TOCTOU (time-of-check\n * to time-of-use) race conditions in distributed systems. Between reading the\n * bucket state and writing the updated state, another process may have modified\n * the bucket. This can result in rate limits being slightly over or under the\n * configured limit.\n *\n * For precise rate limiting in high-concurrency distributed environments,\n * implement the optional `compareAndSet` method with atomic operations:\n *\n * - **Redis**: Use Lua scripts with WATCH/MULTI/EXEC\n * - **DynamoDB**: Use conditional writes with version checks\n * - **Forge Storage**: Accepts eventual consistency\n *\n * For most use cases, the slight inaccuracy from race conditions is acceptable.\n * The rate limiter still provides effective protection against abuse.\n *\n * @example Redis implementation with atomic operations\n * ```typescript\n * const redisStorage: RateLimitStorage = {\n * async get(key) {\n * const data = await redis.get(`ratelimit:${key}`);\n * if (!data) return null;\n * return validateBucketState(JSON.parse(data));\n * },\n * async set(key, state, ttlMs) {\n * const value = JSON.stringify(state);\n * if (ttlMs) {\n * await redis.set(`ratelimit:${key}`, value, 'PX', ttlMs);\n * } else {\n * await redis.set(`ratelimit:${key}`, value);\n * }\n * },\n * async compareAndSet(key, expected, newState, ttlMs) {\n * // Use Lua script for atomic compare-and-set\n * const script = `\n * local current = redis.call('GET', KEYS[1])\n * if current == ARGV[1] or (current == false and ARGV[1] == 'null') then\n * if ARGV[3] then\n * redis.call('SET', KEYS[1], ARGV[2], 'PX', ARGV[3])\n * else\n * redis.call('SET', KEYS[1], ARGV[2])\n * end\n * return {1, ARGV[2]}\n * end\n * return {0, current or 'null'}\n * `;\n * const expectedStr = expected ? JSON.stringify(expected) : 'null';\n * const [success, currentStr] = await redis.eval(script, 1,\n * `ratelimit:${key}`, expectedStr, JSON.stringify(newState), ttlMs?.toString()\n * );\n * return {\n * success: success === 1,\n * currentState: currentStr === 'null' ? null : validateBucketState(JSON.parse(currentStr))\n * };\n * },\n * async delete(key) {\n * await redis.del(`ratelimit:${key}`);\n * },\n * async clear() {\n * const keys = await redis.keys('ratelimit:*');\n * if (keys.length > 0) {\n * await redis.del(...keys);\n * }\n * }\n * };\n * ```\n *\n * @example Forge Storage implementation (accepts eventual consistency)\n * ```typescript\n * import { storage } from '@forge/api';\n *\n * const forgeStorage: RateLimitStorage = {\n * async get(key) {\n * const data = await storage.get(`ratelimit:${key}`);\n * return validateBucketState(data);\n * },\n * async set(key, state) {\n * await storage.set(`ratelimit:${key}`, state);\n * },\n * async delete(key) {\n * await storage.delete(`ratelimit:${key}`);\n * }\n * };\n * ```\n */\nexport interface RateLimitStorage {\n /**\n * Retrieve bucket state for a key.\n *\n * @param key - The rate limiting key (already sanitized)\n * @returns The bucket state, or null if not found\n */\n get(key: string): Promise<BucketState | null>;\n\n /**\n * Store bucket state for a key.\n *\n * @param key - The rate limiting key (already sanitized)\n * @param state - The bucket state to store\n * @param ttlMs - Optional TTL in milliseconds for automatic cleanup.\n * Recommended: set to interval * (burst / rate) * 2 to auto-expire\n * stale buckets while keeping active ones alive.\n */\n set(key: string, state: BucketState, ttlMs?: number): Promise<void>;\n\n /**\n * Atomically compare and set bucket state.\n * Optional - implement for precise rate limiting in distributed systems.\n *\n * This method should atomically:\n * 1. Check if the current state matches `expected`\n * 2. If it matches, update to `newState` and return success\n * 3. If it doesn't match, return failure with the current state\n *\n * @param key - The rate limiting key (already sanitized)\n * @param expected - The expected current state (null if expecting no entry)\n * @param newState - The new state to set if expected matches\n * @param ttlMs - Optional TTL in milliseconds\n * @returns Result indicating success and current state\n */\n compareAndSet?(\n key: string,\n expected: BucketState | null,\n newState: BucketState,\n ttlMs?: number\n ): Promise<CompareAndSetResult>;\n\n /**\n * Delete bucket state for a key.\n * Optional - if not implemented, reset operations on individual keys\n * will not be supported.\n *\n * @param key - The rate limiting key (already sanitized)\n */\n delete?(key: string): Promise<void>;\n\n /**\n * Clear all bucket states.\n * Optional - if not implemented, full reset operations will not clear storage.\n */\n clear?(): Promise<void>;\n}\n\n/**\n * In-memory storage implementation using Map.\n * This is the default storage used when no external storage is provided.\n *\n * Features:\n * - LRU eviction when maxEntries is exceeded\n * - Synchronous operations (async interface for compatibility)\n * - No persistence across process restarts\n * - No race conditions (single-threaded JavaScript)\n *\n * @example\n * ```typescript\n * const storage = new MemoryStorage({ maxEntries: 10000 });\n * const limiter = new RateLimiter({ rate: 100, storage });\n * ```\n */\nexport class MemoryStorage implements RateLimitStorage {\n /** Cached resolved promise for void returns to avoid allocation */\n private static readonly RESOLVED_VOID = Promise.resolve();\n\n /** Cached resolved promise for null returns to avoid allocation */\n private static readonly RESOLVED_NULL: Promise<BucketState | null> = Promise.resolve(null);\n\n private readonly entries = new Map<string, BucketState>();\n private readonly maxEntries: number;\n private evictionCount = 0;\n\n /**\n * Create a new in-memory storage.\n *\n * @param options - Storage options\n * @param options.maxEntries - Maximum entries before LRU eviction (0 = unlimited, default: 10000)\n */\n constructor(options?: { readonly maxEntries?: number }) {\n this.maxEntries = options?.maxEntries ?? 10000;\n }\n\n get(key: string): Promise<BucketState | null> {\n const state = this.getSync(key);\n return state === null ? MemoryStorage.RESOLVED_NULL : Promise.resolve(state);\n }\n\n set(key: string, state: BucketState): Promise<void> {\n this.setSync(key, state);\n return MemoryStorage.RESOLVED_VOID;\n }\n\n compareAndSet(\n key: string,\n expected: BucketState | null,\n newState: BucketState\n ): Promise<CompareAndSetResult> {\n const current = this.entries.get(key) ?? null;\n\n // Check if current matches expected\n const matches =\n current === expected ||\n (current !== null &&\n expected !== null &&\n current.tokens === expected.tokens &&\n current.lastRefill === expected.lastRefill);\n\n if (matches) {\n this.setSync(key, newState);\n return Promise.resolve({ success: true, currentState: newState });\n }\n\n return Promise.resolve({ success: false, currentState: current });\n }\n\n delete(key: string): Promise<void> {\n this.deleteSync(key);\n return MemoryStorage.RESOLVED_VOID;\n }\n\n clear(): Promise<void> {\n this.clearSync();\n return MemoryStorage.RESOLVED_VOID;\n }\n\n /**\n * Get the current number of entries.\n */\n size(): number {\n return this.entries.size;\n }\n\n /**\n * Get the total number of evictions since creation.\n */\n getEvictionCount(): number {\n return this.evictionCount;\n }\n\n /**\n * Synchronous get for backward compatibility with sync rate limiter methods.\n * @internal\n */\n getSync(key: string): BucketState | null {\n const state = this.entries.get(key);\n if (state) {\n // LRU touch: move to end\n this.entries.delete(key);\n this.entries.set(key, state);\n }\n return state ?? null;\n }\n\n /**\n * Synchronous set for backward compatibility with sync rate limiter methods.\n * @internal\n */\n setSync(key: string, state: BucketState): void {\n if (this.entries.has(key)) {\n this.entries.delete(key);\n } else if (this.maxEntries > 0 && this.entries.size >= this.maxEntries) {\n const firstKey = this.entries.keys().next().value;\n if (firstKey !== undefined) {\n this.entries.delete(firstKey);\n this.evictionCount++;\n }\n }\n this.entries.set(key, state);\n }\n\n /**\n * Synchronous delete for backward compatibility.\n * @internal\n */\n deleteSync(key: string): void {\n this.entries.delete(key);\n }\n\n /**\n * Synchronous clear for backward compatibility.\n * @internal\n */\n clearSync(): void {\n this.entries.clear();\n }\n}\n"]} |
+68
-4
@@ -8,2 +8,7 @@ import { z } from 'zod'; | ||
| constructor(message: string); | ||
| /** | ||
| * Serialize to JSON for structured logging. | ||
| * @returns JSON-safe representation of the error | ||
| */ | ||
| toJSON(): Record<string, unknown>; | ||
| } | ||
@@ -18,2 +23,11 @@ /** | ||
| * Error thrown when rate limit is exceeded. | ||
| * | ||
| * **Security Note:** The `key` property contains the rate limiting key | ||
| * (e.g., user ID, IP address). When logging or exposing errors to users, | ||
| * consider whether exposing this value is appropriate for your use case. | ||
| * For sensitive keys, you may want to redact or omit this property when | ||
| * serializing errors for external consumption. | ||
| * | ||
| * The `toJSON()` method excludes the key by default for security. | ||
| * Use `toJSON(true)` to include the key when needed. | ||
| */ | ||
@@ -23,2 +37,9 @@ declare class RateLimitExceededError extends FortifyError { | ||
| constructor(message?: string, key?: string); | ||
| /** | ||
| * Serialize to JSON, optionally including the key. | ||
| * | ||
| * @param includeKey - Whether to include the rate limit key (default: false for security) | ||
| * @returns JSON-safe representation of the error | ||
| */ | ||
| toJSON(includeKey?: boolean): Record<string, unknown>; | ||
| } | ||
@@ -32,2 +53,3 @@ /** | ||
| constructor(message?: string, activeCount?: number, queuedCount?: number); | ||
| toJSON(): Record<string, unknown>; | ||
| } | ||
@@ -40,2 +62,7 @@ /** | ||
| constructor(message?: string, timeoutMs?: number); | ||
| /** | ||
| * Alias for timeoutMs for convenience. | ||
| */ | ||
| get duration(): number; | ||
| toJSON(): Record<string, unknown>; | ||
| } | ||
@@ -49,2 +76,3 @@ /** | ||
| constructor(message?: string, attempts?: number, lastError?: Error); | ||
| toJSON(): Record<string, unknown>; | ||
| } | ||
@@ -175,2 +203,10 @@ /** | ||
| /** | ||
| * A static never-aborted AbortSignal for use when no signal is provided. | ||
| * Avoids allocating a new AbortController on every operation call. | ||
| * | ||
| * This is a performance optimization for hot paths where signal is optional | ||
| * but the operation signature requires AbortSignal. | ||
| */ | ||
| declare const NEVER_ABORTED_SIGNAL: AbortSignal; | ||
| /** | ||
| * Sleep for a specified duration with optional cancellation support. | ||
@@ -238,9 +274,37 @@ * | ||
| /** | ||
| * Calculate jittered delay. | ||
| * Adds 0-10% random variance to prevent thundering herd. | ||
| * Jitter mode for delay randomization. | ||
| * | ||
| * - `full`: Random delay from 0 to base delay (aggressive, best for thundering herd) | ||
| * - `equal`: Random delay from 50% to 100% of base delay (balanced) | ||
| * - `decorrelated`: Each sleep is random between base and previous sleep * 3 (AWS-style) | ||
| */ | ||
| type JitterMode = 'full' | 'equal' | 'decorrelated'; | ||
| /** | ||
| * Calculate jittered delay to prevent thundering herd. | ||
| * | ||
| * Uses "equal jitter" by default: random value between 50-100% of the delay. | ||
| * This provides good dispersion while maintaining reasonable minimum delays. | ||
| * | ||
| * For more aggressive jitter, use mode='full' (0-100% of delay). | ||
| * For AWS-style decorrelated jitter, use mode='decorrelated' with previousDelay. | ||
| * | ||
| * @param delay - Base delay in milliseconds | ||
| * @param mode - Jitter mode: 'full', 'equal', or 'decorrelated' (default: 'equal') | ||
| * @param previousDelay - Previous delay for decorrelated mode (default: delay) | ||
| * @returns Jittered delay | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * // Equal jitter (default): 50-100% of delay | ||
| * addJitter(1000); // Returns 500-1000ms | ||
| * | ||
| * // Full jitter: 0-100% of delay | ||
| * addJitter(1000, 'full'); // Returns 0-1000ms | ||
| * | ||
| * // Decorrelated jitter (AWS-style) | ||
| * let sleep = initialDelay; | ||
| * sleep = addJitter(baseDelay, 'decorrelated', sleep); | ||
| * ``` | ||
| */ | ||
| declare function addJitter(delay: number): number; | ||
| declare function addJitter(delay: number, mode?: JitterMode, previousDelay?: number): number; | ||
| /** | ||
@@ -679,2 +743,2 @@ * Clamp a number between min and max values. | ||
| export { type BackoffPolicy, type BucketState, BulkheadClosedError, type BulkheadConfig, BulkheadFullError, type CircuitBreakerConfig, type CircuitBreakerCounts, type CircuitBreakerState, CircuitOpenError, type Closeable, type CompareAndSetResult, type ErrorCallback, type FallbackConfig, FortifyError, type FortifyLogger, MaxAttemptsReachedError, MemoryStorage, type Operation, type Pattern, type RateLimitCallback, type RateLimitConfig, RateLimitExceededError, type RateLimitStorage, type Resettable, type RetryCallback, type RetryConfig, type RetryableError, RetryableErrorWrapper, type StateChangeCallback, type TimeoutConfig, TimeoutError, type VoidCallback, addJitter, asNonRetryable, asRetryable, backoffPolicySchema, bucketStateSchema, bulkheadConfigSchema, circuitBreakerConfigSchema, circuitBreakerCountsSchema, circuitBreakerStateSchema, clamp, combineSignals, consoleLogger, executeWithTimeout, fallbackConfigSchema, isAbortError, isRetryable, isRetryableError, loggerSchema, noopLogger, now, rateLimitConfigSchema, retryConfigSchema, safeCallback, sanitizeStorageKey, sleep, throwIfAborted, timeoutConfigSchema, validateBucketState, withTimeout }; | ||
| export { type BackoffPolicy, type BucketState, BulkheadClosedError, type BulkheadConfig, BulkheadFullError, type CircuitBreakerConfig, type CircuitBreakerCounts, type CircuitBreakerState, CircuitOpenError, type Closeable, type CompareAndSetResult, type ErrorCallback, type FallbackConfig, FortifyError, type FortifyLogger, type JitterMode, MaxAttemptsReachedError, MemoryStorage, NEVER_ABORTED_SIGNAL, type Operation, type Pattern, type RateLimitCallback, type RateLimitConfig, RateLimitExceededError, type RateLimitStorage, type Resettable, type RetryCallback, type RetryConfig, type RetryableError, RetryableErrorWrapper, type StateChangeCallback, type TimeoutConfig, TimeoutError, type VoidCallback, addJitter, asNonRetryable, asRetryable, backoffPolicySchema, bucketStateSchema, bulkheadConfigSchema, circuitBreakerConfigSchema, circuitBreakerCountsSchema, circuitBreakerStateSchema, clamp, combineSignals, consoleLogger, executeWithTimeout, fallbackConfigSchema, isAbortError, isRetryable, isRetryableError, loggerSchema, noopLogger, now, rateLimitConfigSchema, retryConfigSchema, safeCallback, sanitizeStorageKey, sleep, throwIfAborted, timeoutConfigSchema, validateBucketState, withTimeout }; |
+68
-4
@@ -8,2 +8,7 @@ import { z } from 'zod'; | ||
| constructor(message: string); | ||
| /** | ||
| * Serialize to JSON for structured logging. | ||
| * @returns JSON-safe representation of the error | ||
| */ | ||
| toJSON(): Record<string, unknown>; | ||
| } | ||
@@ -18,2 +23,11 @@ /** | ||
| * Error thrown when rate limit is exceeded. | ||
| * | ||
| * **Security Note:** The `key` property contains the rate limiting key | ||
| * (e.g., user ID, IP address). When logging or exposing errors to users, | ||
| * consider whether exposing this value is appropriate for your use case. | ||
| * For sensitive keys, you may want to redact or omit this property when | ||
| * serializing errors for external consumption. | ||
| * | ||
| * The `toJSON()` method excludes the key by default for security. | ||
| * Use `toJSON(true)` to include the key when needed. | ||
| */ | ||
@@ -23,2 +37,9 @@ declare class RateLimitExceededError extends FortifyError { | ||
| constructor(message?: string, key?: string); | ||
| /** | ||
| * Serialize to JSON, optionally including the key. | ||
| * | ||
| * @param includeKey - Whether to include the rate limit key (default: false for security) | ||
| * @returns JSON-safe representation of the error | ||
| */ | ||
| toJSON(includeKey?: boolean): Record<string, unknown>; | ||
| } | ||
@@ -32,2 +53,3 @@ /** | ||
| constructor(message?: string, activeCount?: number, queuedCount?: number); | ||
| toJSON(): Record<string, unknown>; | ||
| } | ||
@@ -40,2 +62,7 @@ /** | ||
| constructor(message?: string, timeoutMs?: number); | ||
| /** | ||
| * Alias for timeoutMs for convenience. | ||
| */ | ||
| get duration(): number; | ||
| toJSON(): Record<string, unknown>; | ||
| } | ||
@@ -49,2 +76,3 @@ /** | ||
| constructor(message?: string, attempts?: number, lastError?: Error); | ||
| toJSON(): Record<string, unknown>; | ||
| } | ||
@@ -175,2 +203,10 @@ /** | ||
| /** | ||
| * A static never-aborted AbortSignal for use when no signal is provided. | ||
| * Avoids allocating a new AbortController on every operation call. | ||
| * | ||
| * This is a performance optimization for hot paths where signal is optional | ||
| * but the operation signature requires AbortSignal. | ||
| */ | ||
| declare const NEVER_ABORTED_SIGNAL: AbortSignal; | ||
| /** | ||
| * Sleep for a specified duration with optional cancellation support. | ||
@@ -238,9 +274,37 @@ * | ||
| /** | ||
| * Calculate jittered delay. | ||
| * Adds 0-10% random variance to prevent thundering herd. | ||
| * Jitter mode for delay randomization. | ||
| * | ||
| * - `full`: Random delay from 0 to base delay (aggressive, best for thundering herd) | ||
| * - `equal`: Random delay from 50% to 100% of base delay (balanced) | ||
| * - `decorrelated`: Each sleep is random between base and previous sleep * 3 (AWS-style) | ||
| */ | ||
| type JitterMode = 'full' | 'equal' | 'decorrelated'; | ||
| /** | ||
| * Calculate jittered delay to prevent thundering herd. | ||
| * | ||
| * Uses "equal jitter" by default: random value between 50-100% of the delay. | ||
| * This provides good dispersion while maintaining reasonable minimum delays. | ||
| * | ||
| * For more aggressive jitter, use mode='full' (0-100% of delay). | ||
| * For AWS-style decorrelated jitter, use mode='decorrelated' with previousDelay. | ||
| * | ||
| * @param delay - Base delay in milliseconds | ||
| * @param mode - Jitter mode: 'full', 'equal', or 'decorrelated' (default: 'equal') | ||
| * @param previousDelay - Previous delay for decorrelated mode (default: delay) | ||
| * @returns Jittered delay | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * // Equal jitter (default): 50-100% of delay | ||
| * addJitter(1000); // Returns 500-1000ms | ||
| * | ||
| * // Full jitter: 0-100% of delay | ||
| * addJitter(1000, 'full'); // Returns 0-1000ms | ||
| * | ||
| * // Decorrelated jitter (AWS-style) | ||
| * let sleep = initialDelay; | ||
| * sleep = addJitter(baseDelay, 'decorrelated', sleep); | ||
| * ``` | ||
| */ | ||
| declare function addJitter(delay: number): number; | ||
| declare function addJitter(delay: number, mode?: JitterMode, previousDelay?: number): number; | ||
| /** | ||
@@ -679,2 +743,2 @@ * Clamp a number between min and max values. | ||
| export { type BackoffPolicy, type BucketState, BulkheadClosedError, type BulkheadConfig, BulkheadFullError, type CircuitBreakerConfig, type CircuitBreakerCounts, type CircuitBreakerState, CircuitOpenError, type Closeable, type CompareAndSetResult, type ErrorCallback, type FallbackConfig, FortifyError, type FortifyLogger, MaxAttemptsReachedError, MemoryStorage, type Operation, type Pattern, type RateLimitCallback, type RateLimitConfig, RateLimitExceededError, type RateLimitStorage, type Resettable, type RetryCallback, type RetryConfig, type RetryableError, RetryableErrorWrapper, type StateChangeCallback, type TimeoutConfig, TimeoutError, type VoidCallback, addJitter, asNonRetryable, asRetryable, backoffPolicySchema, bucketStateSchema, bulkheadConfigSchema, circuitBreakerConfigSchema, circuitBreakerCountsSchema, circuitBreakerStateSchema, clamp, combineSignals, consoleLogger, executeWithTimeout, fallbackConfigSchema, isAbortError, isRetryable, isRetryableError, loggerSchema, noopLogger, now, rateLimitConfigSchema, retryConfigSchema, safeCallback, sanitizeStorageKey, sleep, throwIfAborted, timeoutConfigSchema, validateBucketState, withTimeout }; | ||
| export { type BackoffPolicy, type BucketState, BulkheadClosedError, type BulkheadConfig, BulkheadFullError, type CircuitBreakerConfig, type CircuitBreakerCounts, type CircuitBreakerState, CircuitOpenError, type Closeable, type CompareAndSetResult, type ErrorCallback, type FallbackConfig, FortifyError, type FortifyLogger, type JitterMode, MaxAttemptsReachedError, MemoryStorage, NEVER_ABORTED_SIGNAL, type Operation, type Pattern, type RateLimitCallback, type RateLimitConfig, RateLimitExceededError, type RateLimitStorage, type Resettable, type RetryCallback, type RetryConfig, type RetryableError, RetryableErrorWrapper, type StateChangeCallback, type TimeoutConfig, TimeoutError, type VoidCallback, addJitter, asNonRetryable, asRetryable, backoffPolicySchema, bucketStateSchema, bulkheadConfigSchema, circuitBreakerConfigSchema, circuitBreakerCountsSchema, circuitBreakerStateSchema, clamp, combineSignals, consoleLogger, executeWithTimeout, fallbackConfigSchema, isAbortError, isRetryable, isRetryableError, loggerSchema, noopLogger, now, rateLimitConfigSchema, retryConfigSchema, safeCallback, sanitizeStorageKey, sleep, throwIfAborted, timeoutConfigSchema, validateBucketState, withTimeout }; |
+85
-26
| import { z } from 'zod'; | ||
| var __defProp = Object.defineProperty; | ||
| var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; | ||
| var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); | ||
| // src/errors.ts | ||
@@ -16,2 +12,12 @@ var FortifyError = class extends Error { | ||
| } | ||
| /** | ||
| * Serialize to JSON for structured logging. | ||
| * @returns JSON-safe representation of the error | ||
| */ | ||
| toJSON() { | ||
| return { | ||
| name: this.name, | ||
| message: this.message | ||
| }; | ||
| } | ||
| }; | ||
@@ -25,14 +31,30 @@ var CircuitOpenError = class extends FortifyError { | ||
| var RateLimitExceededError = class extends FortifyError { | ||
| key; | ||
| constructor(message = "Rate limit exceeded", key) { | ||
| super(message); | ||
| __publicField(this, "key"); | ||
| this.name = "RateLimitExceededError"; | ||
| this.key = key; | ||
| } | ||
| /** | ||
| * Serialize to JSON, optionally including the key. | ||
| * | ||
| * @param includeKey - Whether to include the rate limit key (default: false for security) | ||
| * @returns JSON-safe representation of the error | ||
| */ | ||
| toJSON(includeKey = false) { | ||
| const result = { | ||
| name: this.name, | ||
| message: this.message | ||
| }; | ||
| if (includeKey && this.key !== void 0) { | ||
| result.key = this.key; | ||
| } | ||
| return result; | ||
| } | ||
| }; | ||
| var BulkheadFullError = class extends FortifyError { | ||
| activeCount; | ||
| queuedCount; | ||
| constructor(message = "Bulkhead is full", activeCount = 0, queuedCount = 0) { | ||
| super(message); | ||
| __publicField(this, "activeCount"); | ||
| __publicField(this, "queuedCount"); | ||
| this.name = "BulkheadFullError"; | ||
@@ -42,16 +64,35 @@ this.activeCount = activeCount; | ||
| } | ||
| toJSON() { | ||
| return { | ||
| ...super.toJSON(), | ||
| activeCount: this.activeCount, | ||
| queuedCount: this.queuedCount | ||
| }; | ||
| } | ||
| }; | ||
| var TimeoutError = class extends FortifyError { | ||
| timeoutMs; | ||
| constructor(message = "Operation timed out", timeoutMs = 0) { | ||
| super(message); | ||
| __publicField(this, "timeoutMs"); | ||
| this.name = "TimeoutError"; | ||
| this.timeoutMs = timeoutMs; | ||
| } | ||
| /** | ||
| * Alias for timeoutMs for convenience. | ||
| */ | ||
| get duration() { | ||
| return this.timeoutMs; | ||
| } | ||
| toJSON() { | ||
| return { | ||
| ...super.toJSON(), | ||
| timeoutMs: this.timeoutMs | ||
| }; | ||
| } | ||
| }; | ||
| var MaxAttemptsReachedError = class extends FortifyError { | ||
| attempts; | ||
| lastError; | ||
| constructor(message = "Maximum retry attempts reached", attempts = 0, lastError) { | ||
| super(message); | ||
| __publicField(this, "attempts"); | ||
| __publicField(this, "lastError"); | ||
| this.name = "MaxAttemptsReachedError"; | ||
@@ -61,2 +102,9 @@ this.attempts = attempts; | ||
| } | ||
| toJSON() { | ||
| return { | ||
| ...super.toJSON(), | ||
| attempts: this.attempts, | ||
| lastError: this.lastError ? { name: this.lastError.name, message: this.lastError.message } : void 0 | ||
| }; | ||
| } | ||
| }; | ||
@@ -73,6 +121,6 @@ var BulkheadClosedError = class extends FortifyError { | ||
| var RetryableErrorWrapper = class extends Error { | ||
| retryable; | ||
| cause; | ||
| constructor(error, retryable) { | ||
| super(error.message, { cause: error }); | ||
| __publicField(this, "retryable"); | ||
| __publicField(this, "cause"); | ||
| this.name = "RetryableErrorWrapper"; | ||
@@ -138,2 +186,3 @@ this.retryable = retryable; | ||
| // src/utils.ts | ||
| var NEVER_ABORTED_SIGNAL = new AbortController().signal; | ||
| function sleep(ms, signal) { | ||
@@ -216,3 +265,3 @@ return new Promise((resolve, reject) => { | ||
| if (validSignals.length === 0) { | ||
| return new AbortController().signal; | ||
| return NEVER_ABORTED_SIGNAL; | ||
| } | ||
@@ -269,5 +318,16 @@ const firstSignal = validSignals[0]; | ||
| } | ||
| function addJitter(delay) { | ||
| const jitterFactor = 1 + Math.random() * 0.1; | ||
| return Math.floor(delay * jitterFactor); | ||
| function addJitter(delay, mode = "equal", previousDelay) { | ||
| switch (mode) { | ||
| case "full": | ||
| return Math.floor(Math.random() * delay); | ||
| case "decorrelated": { | ||
| const prev = previousDelay ?? delay; | ||
| const min = delay; | ||
| const max = Math.min(prev * 3, delay * 10); | ||
| return Math.floor(min + Math.random() * (max - min)); | ||
| } | ||
| case "equal": | ||
| default: | ||
| return Math.floor(delay * 0.5 + Math.random() * delay * 0.5); | ||
| } | ||
| } | ||
@@ -398,3 +458,10 @@ function clamp(value, min, max) { | ||
| } | ||
| var _MemoryStorage = class _MemoryStorage { | ||
| var MemoryStorage = class _MemoryStorage { | ||
| /** Cached resolved promise for void returns to avoid allocation */ | ||
| static RESOLVED_VOID = Promise.resolve(); | ||
| /** Cached resolved promise for null returns to avoid allocation */ | ||
| static RESOLVED_NULL = Promise.resolve(null); | ||
| entries = /* @__PURE__ */ new Map(); | ||
| maxEntries; | ||
| evictionCount = 0; | ||
| /** | ||
@@ -407,5 +474,2 @@ * Create a new in-memory storage. | ||
| constructor(options) { | ||
| __publicField(this, "entries", /* @__PURE__ */ new Map()); | ||
| __publicField(this, "maxEntries"); | ||
| __publicField(this, "evictionCount", 0); | ||
| this.maxEntries = options?.maxEntries ?? 1e4; | ||
@@ -493,10 +557,5 @@ } | ||
| }; | ||
| /** Cached resolved promise for void returns to avoid allocation */ | ||
| __publicField(_MemoryStorage, "RESOLVED_VOID", Promise.resolve()); | ||
| /** Cached resolved promise for null returns to avoid allocation */ | ||
| __publicField(_MemoryStorage, "RESOLVED_NULL", Promise.resolve(null)); | ||
| var MemoryStorage = _MemoryStorage; | ||
| export { BulkheadClosedError, BulkheadFullError, CircuitOpenError, FortifyError, MaxAttemptsReachedError, MemoryStorage, RateLimitExceededError, RetryableErrorWrapper, TimeoutError, addJitter, asNonRetryable, asRetryable, backoffPolicySchema, bucketStateSchema, bulkheadConfigSchema, circuitBreakerConfigSchema, circuitBreakerCountsSchema, circuitBreakerStateSchema, clamp, combineSignals, consoleLogger, executeWithTimeout, fallbackConfigSchema, isAbortError, isRetryable, isRetryableError, loggerSchema, noopLogger, now, rateLimitConfigSchema, retryConfigSchema, safeCallback, sanitizeStorageKey, sleep, throwIfAborted, timeoutConfigSchema, validateBucketState, withTimeout }; | ||
| export { BulkheadClosedError, BulkheadFullError, CircuitOpenError, FortifyError, MaxAttemptsReachedError, MemoryStorage, NEVER_ABORTED_SIGNAL, RateLimitExceededError, RetryableErrorWrapper, TimeoutError, addJitter, asNonRetryable, asRetryable, backoffPolicySchema, bucketStateSchema, bulkheadConfigSchema, circuitBreakerConfigSchema, circuitBreakerCountsSchema, circuitBreakerStateSchema, clamp, combineSignals, consoleLogger, executeWithTimeout, fallbackConfigSchema, isAbortError, isRetryable, isRetryableError, loggerSchema, noopLogger, now, rateLimitConfigSchema, retryConfigSchema, safeCallback, sanitizeStorageKey, sleep, throwIfAborted, timeoutConfigSchema, validateBucketState, withTimeout }; | ||
| //# sourceMappingURL=index.js.map | ||
| //# sourceMappingURL=index.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/errors.ts","../src/types.ts","../src/utils.ts","../src/schemas.ts","../src/storage.ts"],"names":["z"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Base error class for all Fortify errors.\n */\nexport class FortifyError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'FortifyError';\n // Maintains proper stack trace for where our error was thrown (only available on V8)\n // Use typeof check to avoid unnecessary-condition lint error\n if (typeof Error.captureStackTrace === 'function') {\n Error.captureStackTrace(this, this.constructor);\n }\n }\n}\n\n/**\n * Error thrown when circuit breaker is in open state and rejecting requests.\n */\nexport class CircuitOpenError extends FortifyError {\n constructor(message = 'Circuit breaker is open') {\n super(message);\n this.name = 'CircuitOpenError';\n }\n}\n\n/**\n * Error thrown when rate limit is exceeded.\n */\nexport class RateLimitExceededError extends FortifyError {\n public readonly key: string | undefined;\n\n constructor(message = 'Rate limit exceeded', key?: string) {\n super(message);\n this.name = 'RateLimitExceededError';\n this.key = key;\n }\n}\n\n/**\n * Error thrown when bulkhead is at capacity.\n */\nexport class BulkheadFullError extends FortifyError {\n public readonly activeCount: number;\n public readonly queuedCount: number;\n\n constructor(\n message = 'Bulkhead is full',\n activeCount = 0,\n queuedCount = 0\n ) {\n super(message);\n this.name = 'BulkheadFullError';\n this.activeCount = activeCount;\n this.queuedCount = queuedCount;\n }\n}\n\n/**\n * Error thrown when operation times out.\n */\nexport class TimeoutError extends FortifyError {\n public readonly timeoutMs: number;\n\n constructor(message = 'Operation timed out', timeoutMs = 0) {\n super(message);\n this.name = 'TimeoutError';\n this.timeoutMs = timeoutMs;\n }\n}\n\n/**\n * Error thrown when maximum retry attempts are reached.\n */\nexport class MaxAttemptsReachedError extends FortifyError {\n public readonly attempts: number;\n public readonly lastError: Error | undefined;\n\n constructor(\n message = 'Maximum retry attempts reached',\n attempts = 0,\n lastError?: Error\n ) {\n super(message);\n this.name = 'MaxAttemptsReachedError';\n this.attempts = attempts;\n this.lastError = lastError;\n }\n}\n\n/**\n * Error thrown when bulkhead is closed and no new operations accepted.\n */\nexport class BulkheadClosedError extends FortifyError {\n constructor(message = 'Bulkhead is closed') {\n super(message);\n this.name = 'BulkheadClosedError';\n }\n}\n\n/**\n * Interface for errors that can indicate whether they are retryable.\n */\nexport interface RetryableError {\n retryable: boolean;\n}\n\n/**\n * Type guard to check if an error implements RetryableError interface.\n */\nexport function isRetryableError(error: unknown): error is Error & RetryableError {\n return (\n error instanceof Error &&\n 'retryable' in error &&\n typeof (error as RetryableError).retryable === 'boolean'\n );\n}\n\n/**\n * Wrapper class to mark an error as retryable.\n */\nexport class RetryableErrorWrapper extends Error implements RetryableError {\n public readonly retryable: boolean;\n public override readonly cause: Error;\n\n constructor(error: Error, retryable: boolean) {\n super(error.message, { cause: error });\n this.name = 'RetryableErrorWrapper';\n this.retryable = retryable;\n this.cause = error;\n if (error.stack) {\n this.stack = error.stack;\n }\n }\n}\n\n/**\n * Wrap an error as retryable.\n */\nexport function asRetryable(error: Error, retryable = true): RetryableErrorWrapper {\n return new RetryableErrorWrapper(error, retryable);\n}\n\n/**\n * Wrap an error as non-retryable.\n */\nexport function asNonRetryable(error: Error): RetryableErrorWrapper {\n return new RetryableErrorWrapper(error, false);\n}\n\n/**\n * Check if an error is retryable.\n * Returns true if the error implements RetryableError and retryable is true.\n * Returns undefined if the error doesn't implement RetryableError (let caller decide).\n */\nexport function isRetryable(error: unknown): boolean | undefined {\n if (isRetryableError(error)) {\n return error.retryable;\n }\n return undefined;\n}\n","/**\n * Represents an async operation that can be executed with cancellation support.\n * The signal parameter allows the operation to be cancelled via AbortController.\n *\n * @template T - The return type of the operation\n */\nexport type Operation<T> = (signal: AbortSignal) => Promise<T>;\n\n/**\n * Callback for state changes in stateful patterns (e.g., circuit breaker).\n *\n * @template S - The state type\n */\nexport type StateChangeCallback<S> = (from: S, to: S) => void;\n\n/**\n * Callback for error events.\n */\nexport type ErrorCallback = (error: Error) => void;\n\n/**\n * Simple void callback.\n */\nexport type VoidCallback = () => void;\n\n/**\n * Callback for retry events.\n */\nexport type RetryCallback = (attempt: number, error: Error) => void;\n\n/**\n * Callback for rate limit events.\n */\nexport type RateLimitCallback = (key: string) => void;\n\n/**\n * Logger interface for structured logging across all patterns.\n * Compatible with pino, winston, console, and custom implementations.\n */\nexport interface FortifyLogger {\n debug(msg: string, context?: Record<string, unknown>): void;\n info(msg: string, context?: Record<string, unknown>): void;\n warn(msg: string, context?: Record<string, unknown>): void;\n error(msg: string, context?: Record<string, unknown>): void;\n}\n\n/**\n * No-operation logger that discards all log messages.\n * Useful for testing or when logging is not needed.\n */\nexport const noopLogger: FortifyLogger = {\n debug: () => undefined,\n info: () => undefined,\n warn: () => undefined,\n error: () => undefined,\n};\n\n/**\n * Console-based logger implementation.\n * Browser-friendly and works in all environments.\n */\nexport const consoleLogger: FortifyLogger = {\n debug: (msg, context) => {\n if (context) {\n console.debug(`[fortify] ${msg}`, context);\n } else {\n console.debug(`[fortify] ${msg}`);\n }\n },\n info: (msg, context) => {\n if (context) {\n console.info(`[fortify] ${msg}`, context);\n } else {\n console.info(`[fortify] ${msg}`);\n }\n },\n warn: (msg, context) => {\n if (context) {\n console.warn(`[fortify] ${msg}`, context);\n } else {\n console.warn(`[fortify] ${msg}`);\n }\n },\n error: (msg, context) => {\n if (context) {\n console.error(`[fortify] ${msg}`, context);\n } else {\n console.error(`[fortify] ${msg}`);\n }\n },\n};\n\n/**\n * Generic pattern interface that all resilience patterns implement.\n *\n * @template T - The return type of the operation\n */\nexport interface Pattern<T> {\n /**\n * Execute an operation with the pattern's resilience logic.\n *\n * @param operation - The async operation to execute\n * @param signal - Optional AbortSignal for cancellation\n * @returns Promise resolving to the operation result\n */\n execute(operation: Operation<T>, signal?: AbortSignal): Promise<T>;\n}\n\n/**\n * Interface for patterns that support closing/cleanup.\n */\nexport interface Closeable {\n /**\n * Close the pattern and release resources.\n * May wait for in-flight operations to complete.\n */\n close(): Promise<void>;\n}\n\n/**\n * Interface for patterns that support resetting state.\n */\nexport interface Resettable {\n /**\n * Reset the pattern to its initial state.\n */\n reset(): void;\n}\n","import { TimeoutError } from './errors.js';\n\n/**\n * Sleep for a specified duration with optional cancellation support.\n *\n * @param ms - Duration in milliseconds\n * @param signal - Optional AbortSignal for cancellation\n * @returns Promise that resolves after the delay or rejects if cancelled\n */\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(ensureError(signal.reason));\n return;\n }\n\n const timeoutId = setTimeout(resolve, ms);\n\n signal?.addEventListener(\n 'abort',\n () => {\n clearTimeout(timeoutId);\n reject(ensureError(signal.reason));\n },\n { once: true }\n );\n });\n}\n\n/**\n * Ensure a value is an Error instance.\n * @param reason - The reason to convert to an Error\n * @returns An Error instance\n */\nfunction ensureError(reason: unknown): Error {\n if (reason instanceof Error) {\n return reason;\n }\n if (typeof reason === 'string') {\n return new Error(reason);\n }\n if (reason !== undefined && reason !== null) {\n // Handle objects and other types safely\n try {\n return new Error(JSON.stringify(reason));\n } catch {\n return new Error('Unknown error');\n }\n }\n return new DOMException('Aborted', 'AbortError');\n}\n\n/**\n * Wrap a promise with a timeout.\n *\n * @template T - The promise return type\n * @param promise - The promise to wrap\n * @param ms - Timeout duration in milliseconds\n * @param signal - Optional external AbortSignal for cancellation\n * @returns Promise that resolves with the result or rejects with TimeoutError\n */\nexport async function withTimeout<T>(\n promise: Promise<T>,\n ms: number,\n signal?: AbortSignal\n): Promise<T> {\n // Check if already aborted\n if (signal?.aborted) {\n throw ensureError(signal.reason);\n }\n\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n reject(new TimeoutError(`Operation timed out after ${String(ms)}ms`, ms));\n }, ms);\n });\n\n // Handle external signal\n const abortPromise = signal\n ? new Promise<never>((_, reject) => {\n signal.addEventListener(\n 'abort',\n () => reject(ensureError(signal.reason)),\n { once: true }\n );\n })\n : null;\n\n try {\n const racers: Promise<T>[] = [promise, timeoutPromise];\n if (abortPromise) {\n racers.push(abortPromise);\n }\n return await Promise.race(racers);\n } finally {\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n }\n }\n}\n\n/**\n * Execute an operation with a timeout, passing the combined signal to the operation.\n *\n * @template T - The operation return type\n * @param operation - The operation to execute\n * @param ms - Timeout duration in milliseconds\n * @param signal - Optional external AbortSignal for cancellation\n * @returns Promise that resolves with the result or rejects with TimeoutError\n */\nexport async function executeWithTimeout<T>(\n operation: (signal: AbortSignal) => Promise<T>,\n ms: number,\n signal?: AbortSignal\n): Promise<T> {\n const controller = new AbortController();\n\n // Combine signals\n const combinedSignal = combineSignals(signal, controller.signal);\n\n const timeoutId = setTimeout(() => {\n controller.abort(new TimeoutError(`Operation timed out after ${String(ms)}ms`, ms));\n }, ms);\n\n try {\n return await operation(combinedSignal);\n } finally {\n clearTimeout(timeoutId);\n }\n}\n\n/**\n * Combine multiple AbortSignals into one.\n * Returns a signal that aborts when any of the input signals abort.\n *\n * Uses AbortSignal.any() when available (Node 20+, modern browsers).\n * Falls back to a manual implementation that properly cleans up event listeners.\n *\n * @param signals - AbortSignals to combine (undefined values are filtered out)\n * @returns Combined AbortSignal\n */\nexport function combineSignals(...signals: (AbortSignal | undefined)[]): AbortSignal {\n const validSignals = signals.filter((s): s is AbortSignal => s !== undefined);\n\n if (validSignals.length === 0) {\n return new AbortController().signal;\n }\n\n const firstSignal = validSignals[0];\n if (validSignals.length === 1 && firstSignal) {\n return firstSignal;\n }\n\n // Use AbortSignal.any if available (Node 20+, modern browsers)\n if ('any' in AbortSignal) {\n return AbortSignal.any(validSignals);\n }\n\n // Fallback: create a new controller and link it to all signals\n // Track listeners so we can clean them up to prevent memory leaks\n const controller = new AbortController();\n const listeners: { signal: AbortSignal; listener: () => void }[] = [];\n\n // Cleanup function to remove all listeners\n const cleanup = () => {\n for (const { signal, listener } of listeners) {\n signal.removeEventListener('abort', listener);\n }\n listeners.length = 0;\n };\n\n for (const signal of validSignals) {\n if (signal.aborted) {\n controller.abort(signal.reason);\n return controller.signal;\n }\n\n const listener = () => {\n cleanup(); // Clean up all listeners when any signal aborts\n controller.abort(signal.reason);\n };\n\n listeners.push({ signal, listener });\n signal.addEventListener('abort', listener, { once: true });\n }\n\n return controller.signal;\n}\n\n/**\n * Check if an error is an abort error (from AbortController).\n *\n * @param error - Error to check\n * @returns True if the error is an AbortError\n */\nexport function isAbortError(error: unknown): boolean {\n return (\n error instanceof DOMException &&\n error.name === 'AbortError'\n );\n}\n\n/**\n * Throw if the given signal is aborted.\n * Useful for checking signal state early in operations to avoid unnecessary work.\n *\n * @param signal - AbortSignal to check\n * @throws {DOMException} If signal is aborted (AbortError)\n */\nexport function throwIfAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted) {\n throw signal.reason ?? new DOMException('Aborted', 'AbortError');\n }\n}\n\n/**\n * Safely execute a callback, catching and logging any errors.\n * Used for user-provided callbacks to prevent them from breaking the pattern.\n *\n * @param callback - Callback to execute\n * @param onError - Optional error handler\n */\nexport function safeCallback<T extends (...args: unknown[]) => unknown>(\n callback: T | undefined,\n onError?: (error: Error) => void\n): T | undefined {\n if (!callback) return undefined;\n\n return ((...args: Parameters<T>): ReturnType<T> | undefined => {\n try {\n return callback(...args) as ReturnType<T>;\n } catch (error) {\n if (onError && error instanceof Error) {\n onError(error);\n }\n return undefined;\n }\n }) as T;\n}\n\n/**\n * Calculate jittered delay.\n * Adds 0-10% random variance to prevent thundering herd.\n *\n * @param delay - Base delay in milliseconds\n * @returns Jittered delay\n */\nexport function addJitter(delay: number): number {\n const jitterFactor = 1 + Math.random() * 0.1; // 0-10% jitter\n return Math.floor(delay * jitterFactor);\n}\n\n/**\n * Clamp a number between min and max values.\n *\n * @param value - Value to clamp\n * @param min - Minimum value\n * @param max - Maximum value\n * @returns Clamped value\n */\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n\n/**\n * Get current timestamp in milliseconds.\n * Uses performance.now() if available for higher precision.\n *\n * @returns Current timestamp in milliseconds\n */\nexport function now(): number {\n if (typeof performance !== 'undefined' && typeof performance.now === 'function') {\n return performance.now();\n }\n return Date.now();\n}\n","import { z } from 'zod';\n\n/**\n * Schema for a function type (Zod 4 compatible).\n * Uses z.function() which validates that the value is a function.\n */\nconst functionSchema = z.function();\n\n/**\n * Base schema for logger configuration.\n * Validates that the logger has the required methods.\n */\nexport const loggerSchema = z.object({\n debug: functionSchema,\n info: functionSchema,\n warn: functionSchema,\n error: functionSchema,\n}).optional();\n\n/**\n * Schema for timeout configuration.\n */\nexport const timeoutConfigSchema = z.object({\n /** Default timeout in milliseconds (default: 30000) */\n defaultTimeout: z.number().int().positive().default(30000),\n /** Callback when timeout occurs */\n onTimeout: functionSchema.optional(),\n /** Logger instance */\n logger: loggerSchema,\n});\n\nexport type TimeoutConfig = z.infer<typeof timeoutConfigSchema>;\n\n/**\n * Schema for retry backoff policy.\n */\nexport const backoffPolicySchema = z.enum(['exponential', 'linear', 'constant']);\n\nexport type BackoffPolicy = z.infer<typeof backoffPolicySchema>;\n\n/**\n * Schema for retry configuration.\n */\nexport const retryConfigSchema = z.object({\n /** Maximum number of attempts including the first (default: 3) */\n maxAttempts: z.number().int().positive().default(3),\n /** Initial delay before first retry in milliseconds (default: 100) */\n initialDelay: z.number().int().positive().default(100),\n /** Maximum delay between retries in milliseconds */\n maxDelay: z.number().int().positive().optional(),\n /** Backoff strategy (default: 'exponential') */\n backoffPolicy: backoffPolicySchema.default('exponential'),\n /** Multiplier for exponential backoff (default: 2.0) */\n multiplier: z.number().positive().default(2.0),\n /** Add random jitter to delays (default: false) */\n jitter: z.boolean().default(false),\n /** Custom function to determine if error is retryable */\n isRetryable: functionSchema.optional(),\n /** Callback on each retry attempt */\n onRetry: functionSchema.optional(),\n /** Logger instance */\n logger: loggerSchema,\n});\n\nexport type RetryConfig = z.infer<typeof retryConfigSchema>;\n\n/**\n * Schema for circuit breaker state.\n */\nexport const circuitBreakerStateSchema = z.enum(['closed', 'open', 'half-open']);\n\nexport type CircuitBreakerState = z.infer<typeof circuitBreakerStateSchema>;\n\n/**\n * Schema for circuit breaker counts/metrics.\n */\nexport const circuitBreakerCountsSchema = z.object({\n requests: z.number().int().nonnegative(),\n totalSuccesses: z.number().int().nonnegative(),\n totalFailures: z.number().int().nonnegative(),\n consecutiveSuccesses: z.number().int().nonnegative(),\n consecutiveFailures: z.number().int().nonnegative(),\n});\n\nexport type CircuitBreakerCounts = z.infer<typeof circuitBreakerCountsSchema>;\n\n/**\n * Schema for circuit breaker configuration.\n */\nexport const circuitBreakerConfigSchema = z.object({\n /** Maximum consecutive failures before opening (default: 5) */\n maxFailures: z.number().int().positive().default(5),\n /** Duration in open state before transitioning to half-open in milliseconds (default: 60000) */\n timeout: z.number().int().positive().default(60000),\n /** Maximum requests allowed in half-open state (default: 1) */\n halfOpenMaxRequests: z.number().int().positive().default(1),\n /** Period to clear counts when closed, 0 means never (default: 0) */\n interval: z.number().int().nonnegative().default(0),\n /** Custom function to determine when to trip the breaker */\n readyToTrip: functionSchema.optional(),\n /** Custom function to determine if result is successful */\n isSuccessful: functionSchema.optional(),\n /** Callback on state change */\n onStateChange: functionSchema.optional(),\n /** Logger instance */\n logger: loggerSchema,\n});\n\nexport type CircuitBreakerConfig = z.infer<typeof circuitBreakerConfigSchema>;\n\n/**\n * Schema for rate limiter configuration.\n */\nexport const rateLimitConfigSchema = z.object({\n /** Number of tokens added per interval (default: 100) */\n rate: z.number().int().positive().default(100),\n /** Maximum bucket capacity (defaults to rate) */\n burst: z.number().int().positive().optional(),\n /** Interval for token refill in milliseconds (default: 1000) */\n interval: z.number().int().positive().default(1000),\n /** Callback when rate limit is hit */\n onLimit: functionSchema.optional(),\n /** Logger instance */\n logger: loggerSchema,\n});\n\nexport type RateLimitConfig = z.infer<typeof rateLimitConfigSchema>;\n\n/**\n * Schema for bulkhead configuration.\n */\nexport const bulkheadConfigSchema = z.object({\n /** Maximum concurrent executions (default: 10) */\n maxConcurrent: z.number().int().positive().default(10),\n /** Maximum queued requests, 0 means no queueing (default: 0) */\n maxQueue: z.number().int().nonnegative().default(0),\n /** Maximum time to wait in queue in milliseconds */\n queueTimeout: z.number().int().positive().optional(),\n /** Callback when request is rejected */\n onRejected: functionSchema.optional(),\n /** Logger instance */\n logger: loggerSchema,\n});\n\nexport type BulkheadConfig = z.infer<typeof bulkheadConfigSchema>;\n\n/**\n * Schema for fallback configuration.\n */\nexport const fallbackConfigSchema = z.object({\n /** Fallback function to execute when primary fails */\n fallback: functionSchema,\n /** Custom function to determine if fallback should be used */\n shouldFallback: functionSchema.optional(),\n /** Callback when fallback is triggered */\n onFallback: functionSchema.optional(),\n /** Callback when primary succeeds */\n onSuccess: functionSchema.optional(),\n /** Logger instance */\n logger: loggerSchema,\n});\n\nexport type FallbackConfig = z.infer<typeof fallbackConfigSchema>;\n","import { z } from 'zod';\n\n/** Maximum reasonable token count (1 billion) */\nconst MAX_TOKENS = 1_000_000_000;\n\n/** Maximum reasonable timestamp (year 2100 in ms) */\nconst MAX_TIMESTAMP = 4_102_444_800_000;\n\n/**\n * Zod schema for validating bucket state from external storage.\n * Use this to validate data retrieved from untrusted storage sources.\n * Includes bounds checking to prevent malicious or corrupted data.\n */\nexport const bucketStateSchema = z.object({\n tokens: z.number().nonnegative().max(MAX_TOKENS),\n lastRefill: z.number().int().nonnegative().max(MAX_TIMESTAMP),\n});\n\n/**\n * State of a rate limit bucket for external storage.\n * Contains all data needed to reconstruct bucket state across invocations.\n */\nexport interface BucketState {\n /** Current number of tokens in the bucket */\n readonly tokens: number;\n /** Timestamp (ms since epoch) of last token refill */\n readonly lastRefill: number;\n}\n\n/**\n * Validates bucket state from external storage.\n * Returns the validated state or null if invalid.\n *\n * @param data - Raw data from storage\n * @returns Validated BucketState or null if invalid\n *\n * @example\n * ```typescript\n * const data = await redis.get(key);\n * const state = validateBucketState(JSON.parse(data));\n * if (state === null) {\n * // Invalid data, treat as new bucket\n * }\n * ```\n */\nexport function validateBucketState(data: unknown): BucketState | null {\n const result = bucketStateSchema.safeParse(data);\n return result.success ? result.data : null;\n}\n\n/**\n * Result of a compare-and-set operation.\n */\nexport interface CompareAndSetResult {\n /** Whether the update was successful */\n readonly success: boolean;\n /** The current state (after operation) */\n readonly currentState: BucketState | null;\n}\n\n/** Maximum key length after sanitization */\nconst MAX_KEY_LENGTH = 256;\n\n/** Pre-compiled regex for control characters (for performance) */\n// eslint-disable-next-line no-control-regex\nconst CONTROL_CHARS_REGEX = /[\\x00-\\x1f\\x7f]/g;\n\n/** Pre-compiled regex for path separators (for performance) */\nconst PATH_SEPARATORS_REGEX = /[/\\\\]/g;\n\n/**\n * Sanitizes a storage key to prevent injection attacks.\n * Removes or encodes potentially dangerous characters.\n *\n * Optimization: Truncates before regex processing to limit work on long strings.\n *\n * @param key - Raw key from user input\n * @returns Sanitized key safe for storage operations\n *\n * @example\n * ```typescript\n * const safeKey = sanitizeStorageKey(userProvidedKey);\n * await storage.get(safeKey);\n * ```\n */\nexport function sanitizeStorageKey(key: string): string {\n // Truncate first to limit regex work on long strings\n const truncated = key.length > MAX_KEY_LENGTH ? key.slice(0, MAX_KEY_LENGTH) : key;\n\n // Replace control characters, null bytes, and path separators\n // Keep alphanumeric, dash, underscore, dot, colon, at\n return truncated\n .replace(CONTROL_CHARS_REGEX, '') // Remove control characters\n .replace(PATH_SEPARATORS_REGEX, '_'); // Replace path separators\n}\n\n/**\n * Storage adapter interface for rate limiting.\n *\n * Implement this interface to persist rate limit state across serverless\n * invocations or distributed systems. The storage adapter enables rate\n * limiting in environments where in-memory state doesn't persist.\n *\n * ## Concurrency Warning\n *\n * **Important:** The basic get/set interface has inherent TOCTOU (time-of-check\n * to time-of-use) race conditions in distributed systems. Between reading the\n * bucket state and writing the updated state, another process may have modified\n * the bucket. This can result in rate limits being slightly over or under the\n * configured limit.\n *\n * For precise rate limiting in high-concurrency distributed environments,\n * implement the optional `compareAndSet` method with atomic operations:\n *\n * - **Redis**: Use Lua scripts with WATCH/MULTI/EXEC\n * - **DynamoDB**: Use conditional writes with version checks\n * - **Forge Storage**: Accepts eventual consistency\n *\n * For most use cases, the slight inaccuracy from race conditions is acceptable.\n * The rate limiter still provides effective protection against abuse.\n *\n * @example Redis implementation with atomic operations\n * ```typescript\n * const redisStorage: RateLimitStorage = {\n * async get(key) {\n * const data = await redis.get(`ratelimit:${key}`);\n * if (!data) return null;\n * return validateBucketState(JSON.parse(data));\n * },\n * async set(key, state, ttlMs) {\n * const value = JSON.stringify(state);\n * if (ttlMs) {\n * await redis.set(`ratelimit:${key}`, value, 'PX', ttlMs);\n * } else {\n * await redis.set(`ratelimit:${key}`, value);\n * }\n * },\n * async compareAndSet(key, expected, newState, ttlMs) {\n * // Use Lua script for atomic compare-and-set\n * const script = `\n * local current = redis.call('GET', KEYS[1])\n * if current == ARGV[1] or (current == false and ARGV[1] == 'null') then\n * if ARGV[3] then\n * redis.call('SET', KEYS[1], ARGV[2], 'PX', ARGV[3])\n * else\n * redis.call('SET', KEYS[1], ARGV[2])\n * end\n * return {1, ARGV[2]}\n * end\n * return {0, current or 'null'}\n * `;\n * const expectedStr = expected ? JSON.stringify(expected) : 'null';\n * const [success, currentStr] = await redis.eval(script, 1,\n * `ratelimit:${key}`, expectedStr, JSON.stringify(newState), ttlMs?.toString()\n * );\n * return {\n * success: success === 1,\n * currentState: currentStr === 'null' ? null : validateBucketState(JSON.parse(currentStr))\n * };\n * },\n * async delete(key) {\n * await redis.del(`ratelimit:${key}`);\n * },\n * async clear() {\n * const keys = await redis.keys('ratelimit:*');\n * if (keys.length > 0) {\n * await redis.del(...keys);\n * }\n * }\n * };\n * ```\n *\n * @example Forge Storage implementation (accepts eventual consistency)\n * ```typescript\n * import { storage } from '@forge/api';\n *\n * const forgeStorage: RateLimitStorage = {\n * async get(key) {\n * const data = await storage.get(`ratelimit:${key}`);\n * return validateBucketState(data);\n * },\n * async set(key, state) {\n * await storage.set(`ratelimit:${key}`, state);\n * },\n * async delete(key) {\n * await storage.delete(`ratelimit:${key}`);\n * }\n * };\n * ```\n */\nexport interface RateLimitStorage {\n /**\n * Retrieve bucket state for a key.\n *\n * @param key - The rate limiting key (already sanitized)\n * @returns The bucket state, or null if not found\n */\n get(key: string): Promise<BucketState | null>;\n\n /**\n * Store bucket state for a key.\n *\n * @param key - The rate limiting key (already sanitized)\n * @param state - The bucket state to store\n * @param ttlMs - Optional TTL in milliseconds for automatic cleanup.\n * Recommended: set to interval * (burst / rate) * 2 to auto-expire\n * stale buckets while keeping active ones alive.\n */\n set(key: string, state: BucketState, ttlMs?: number): Promise<void>;\n\n /**\n * Atomically compare and set bucket state.\n * Optional - implement for precise rate limiting in distributed systems.\n *\n * This method should atomically:\n * 1. Check if the current state matches `expected`\n * 2. If it matches, update to `newState` and return success\n * 3. If it doesn't match, return failure with the current state\n *\n * @param key - The rate limiting key (already sanitized)\n * @param expected - The expected current state (null if expecting no entry)\n * @param newState - The new state to set if expected matches\n * @param ttlMs - Optional TTL in milliseconds\n * @returns Result indicating success and current state\n */\n compareAndSet?(\n key: string,\n expected: BucketState | null,\n newState: BucketState,\n ttlMs?: number\n ): Promise<CompareAndSetResult>;\n\n /**\n * Delete bucket state for a key.\n * Optional - if not implemented, reset operations on individual keys\n * will not be supported.\n *\n * @param key - The rate limiting key (already sanitized)\n */\n delete?(key: string): Promise<void>;\n\n /**\n * Clear all bucket states.\n * Optional - if not implemented, full reset operations will not clear storage.\n */\n clear?(): Promise<void>;\n}\n\n/**\n * In-memory storage implementation using Map.\n * This is the default storage used when no external storage is provided.\n *\n * Features:\n * - LRU eviction when maxEntries is exceeded\n * - Synchronous operations (async interface for compatibility)\n * - No persistence across process restarts\n * - No race conditions (single-threaded JavaScript)\n *\n * @example\n * ```typescript\n * const storage = new MemoryStorage({ maxEntries: 10000 });\n * const limiter = new RateLimiter({ rate: 100, storage });\n * ```\n */\nexport class MemoryStorage implements RateLimitStorage {\n /** Cached resolved promise for void returns to avoid allocation */\n private static readonly RESOLVED_VOID = Promise.resolve();\n\n /** Cached resolved promise for null returns to avoid allocation */\n private static readonly RESOLVED_NULL: Promise<BucketState | null> = Promise.resolve(null);\n\n private readonly entries = new Map<string, BucketState>();\n private readonly maxEntries: number;\n private evictionCount = 0;\n\n /**\n * Create a new in-memory storage.\n *\n * @param options - Storage options\n * @param options.maxEntries - Maximum entries before LRU eviction (0 = unlimited, default: 10000)\n */\n constructor(options?: { readonly maxEntries?: number }) {\n this.maxEntries = options?.maxEntries ?? 10000;\n }\n\n get(key: string): Promise<BucketState | null> {\n const state = this.getSync(key);\n return state === null ? MemoryStorage.RESOLVED_NULL : Promise.resolve(state);\n }\n\n set(key: string, state: BucketState): Promise<void> {\n this.setSync(key, state);\n return MemoryStorage.RESOLVED_VOID;\n }\n\n compareAndSet(\n key: string,\n expected: BucketState | null,\n newState: BucketState\n ): Promise<CompareAndSetResult> {\n const current = this.entries.get(key) ?? null;\n\n // Check if current matches expected\n const matches =\n current === expected ||\n (current !== null &&\n expected !== null &&\n current.tokens === expected.tokens &&\n current.lastRefill === expected.lastRefill);\n\n if (matches) {\n this.setSync(key, newState);\n return Promise.resolve({ success: true, currentState: newState });\n }\n\n return Promise.resolve({ success: false, currentState: current });\n }\n\n delete(key: string): Promise<void> {\n this.deleteSync(key);\n return MemoryStorage.RESOLVED_VOID;\n }\n\n clear(): Promise<void> {\n this.clearSync();\n return MemoryStorage.RESOLVED_VOID;\n }\n\n /**\n * Get the current number of entries.\n */\n size(): number {\n return this.entries.size;\n }\n\n /**\n * Get the total number of evictions since creation.\n */\n getEvictionCount(): number {\n return this.evictionCount;\n }\n\n /**\n * Synchronous get for backward compatibility with sync rate limiter methods.\n * @internal\n */\n getSync(key: string): BucketState | null {\n const state = this.entries.get(key);\n if (state) {\n // LRU touch: move to end\n this.entries.delete(key);\n this.entries.set(key, state);\n }\n return state ?? null;\n }\n\n /**\n * Synchronous set for backward compatibility with sync rate limiter methods.\n * @internal\n */\n setSync(key: string, state: BucketState): void {\n if (this.entries.has(key)) {\n this.entries.delete(key);\n } else if (this.maxEntries > 0 && this.entries.size >= this.maxEntries) {\n const firstKey = this.entries.keys().next().value;\n if (firstKey !== undefined) {\n this.entries.delete(firstKey);\n this.evictionCount++;\n }\n }\n this.entries.set(key, state);\n }\n\n /**\n * Synchronous delete for backward compatibility.\n * @internal\n */\n deleteSync(key: string): void {\n this.entries.delete(key);\n }\n\n /**\n * Synchronous clear for backward compatibility.\n * @internal\n */\n clearSync(): void {\n this.entries.clear();\n }\n}\n"]} | ||
| {"version":3,"sources":["../src/errors.ts","../src/types.ts","../src/utils.ts","../src/schemas.ts","../src/storage.ts"],"names":["z"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Base error class for all Fortify errors.\n */\nexport class FortifyError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'FortifyError';\n // Maintains proper stack trace for where our error was thrown (only available on V8)\n // Use typeof check to avoid unnecessary-condition lint error\n if (typeof Error.captureStackTrace === 'function') {\n Error.captureStackTrace(this, this.constructor);\n }\n }\n\n /**\n * Serialize to JSON for structured logging.\n * @returns JSON-safe representation of the error\n */\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n message: this.message,\n };\n }\n}\n\n/**\n * Error thrown when circuit breaker is in open state and rejecting requests.\n */\nexport class CircuitOpenError extends FortifyError {\n constructor(message = 'Circuit breaker is open') {\n super(message);\n this.name = 'CircuitOpenError';\n }\n}\n\n/**\n * Error thrown when rate limit is exceeded.\n *\n * **Security Note:** The `key` property contains the rate limiting key\n * (e.g., user ID, IP address). When logging or exposing errors to users,\n * consider whether exposing this value is appropriate for your use case.\n * For sensitive keys, you may want to redact or omit this property when\n * serializing errors for external consumption.\n *\n * The `toJSON()` method excludes the key by default for security.\n * Use `toJSON(true)` to include the key when needed.\n */\nexport class RateLimitExceededError extends FortifyError {\n public readonly key: string | undefined;\n\n constructor(message = 'Rate limit exceeded', key?: string) {\n super(message);\n this.name = 'RateLimitExceededError';\n this.key = key;\n }\n\n /**\n * Serialize to JSON, optionally including the key.\n *\n * @param includeKey - Whether to include the rate limit key (default: false for security)\n * @returns JSON-safe representation of the error\n */\n override toJSON(includeKey = false): Record<string, unknown> {\n const result: Record<string, unknown> = {\n name: this.name,\n message: this.message,\n };\n if (includeKey && this.key !== undefined) {\n result.key = this.key;\n }\n return result;\n }\n}\n\n/**\n * Error thrown when bulkhead is at capacity.\n */\nexport class BulkheadFullError extends FortifyError {\n public readonly activeCount: number;\n public readonly queuedCount: number;\n\n constructor(\n message = 'Bulkhead is full',\n activeCount = 0,\n queuedCount = 0\n ) {\n super(message);\n this.name = 'BulkheadFullError';\n this.activeCount = activeCount;\n this.queuedCount = queuedCount;\n }\n\n override toJSON(): Record<string, unknown> {\n return {\n ...super.toJSON(),\n activeCount: this.activeCount,\n queuedCount: this.queuedCount,\n };\n }\n}\n\n/**\n * Error thrown when operation times out.\n */\nexport class TimeoutError extends FortifyError {\n public readonly timeoutMs: number;\n\n constructor(message = 'Operation timed out', timeoutMs = 0) {\n super(message);\n this.name = 'TimeoutError';\n this.timeoutMs = timeoutMs;\n }\n\n /**\n * Alias for timeoutMs for convenience.\n */\n get duration(): number {\n return this.timeoutMs;\n }\n\n override toJSON(): Record<string, unknown> {\n return {\n ...super.toJSON(),\n timeoutMs: this.timeoutMs,\n };\n }\n}\n\n/**\n * Error thrown when maximum retry attempts are reached.\n */\nexport class MaxAttemptsReachedError extends FortifyError {\n public readonly attempts: number;\n public readonly lastError: Error | undefined;\n\n constructor(\n message = 'Maximum retry attempts reached',\n attempts = 0,\n lastError?: Error\n ) {\n super(message);\n this.name = 'MaxAttemptsReachedError';\n this.attempts = attempts;\n this.lastError = lastError;\n }\n\n override toJSON(): Record<string, unknown> {\n return {\n ...super.toJSON(),\n attempts: this.attempts,\n lastError: this.lastError\n ? { name: this.lastError.name, message: this.lastError.message }\n : undefined,\n };\n }\n}\n\n/**\n * Error thrown when bulkhead is closed and no new operations accepted.\n */\nexport class BulkheadClosedError extends FortifyError {\n constructor(message = 'Bulkhead is closed') {\n super(message);\n this.name = 'BulkheadClosedError';\n }\n}\n\n/**\n * Interface for errors that can indicate whether they are retryable.\n */\nexport interface RetryableError {\n retryable: boolean;\n}\n\n/**\n * Type guard to check if an error implements RetryableError interface.\n */\nexport function isRetryableError(error: unknown): error is Error & RetryableError {\n return (\n error instanceof Error &&\n 'retryable' in error &&\n typeof (error as RetryableError).retryable === 'boolean'\n );\n}\n\n/**\n * Wrapper class to mark an error as retryable.\n */\nexport class RetryableErrorWrapper extends Error implements RetryableError {\n public readonly retryable: boolean;\n public override readonly cause: Error;\n\n constructor(error: Error, retryable: boolean) {\n super(error.message, { cause: error });\n this.name = 'RetryableErrorWrapper';\n this.retryable = retryable;\n this.cause = error;\n if (error.stack) {\n this.stack = error.stack;\n }\n }\n}\n\n/**\n * Wrap an error as retryable.\n */\nexport function asRetryable(error: Error, retryable = true): RetryableErrorWrapper {\n return new RetryableErrorWrapper(error, retryable);\n}\n\n/**\n * Wrap an error as non-retryable.\n */\nexport function asNonRetryable(error: Error): RetryableErrorWrapper {\n return new RetryableErrorWrapper(error, false);\n}\n\n/**\n * Check if an error is retryable.\n * Returns true if the error implements RetryableError and retryable is true.\n * Returns undefined if the error doesn't implement RetryableError (let caller decide).\n */\nexport function isRetryable(error: unknown): boolean | undefined {\n if (isRetryableError(error)) {\n return error.retryable;\n }\n return undefined;\n}\n","/**\n * Represents an async operation that can be executed with cancellation support.\n * The signal parameter allows the operation to be cancelled via AbortController.\n *\n * @template T - The return type of the operation\n */\nexport type Operation<T> = (signal: AbortSignal) => Promise<T>;\n\n/**\n * Callback for state changes in stateful patterns (e.g., circuit breaker).\n *\n * @template S - The state type\n */\nexport type StateChangeCallback<S> = (from: S, to: S) => void;\n\n/**\n * Callback for error events.\n */\nexport type ErrorCallback = (error: Error) => void;\n\n/**\n * Simple void callback.\n */\nexport type VoidCallback = () => void;\n\n/**\n * Callback for retry events.\n */\nexport type RetryCallback = (attempt: number, error: Error) => void;\n\n/**\n * Callback for rate limit events.\n */\nexport type RateLimitCallback = (key: string) => void;\n\n/**\n * Logger interface for structured logging across all patterns.\n * Compatible with pino, winston, console, and custom implementations.\n */\nexport interface FortifyLogger {\n debug(msg: string, context?: Record<string, unknown>): void;\n info(msg: string, context?: Record<string, unknown>): void;\n warn(msg: string, context?: Record<string, unknown>): void;\n error(msg: string, context?: Record<string, unknown>): void;\n}\n\n/**\n * No-operation logger that discards all log messages.\n * Useful for testing or when logging is not needed.\n */\nexport const noopLogger: FortifyLogger = {\n debug: () => undefined,\n info: () => undefined,\n warn: () => undefined,\n error: () => undefined,\n};\n\n/**\n * Console-based logger implementation.\n * Browser-friendly and works in all environments.\n */\nexport const consoleLogger: FortifyLogger = {\n debug: (msg, context) => {\n if (context) {\n console.debug(`[fortify] ${msg}`, context);\n } else {\n console.debug(`[fortify] ${msg}`);\n }\n },\n info: (msg, context) => {\n if (context) {\n console.info(`[fortify] ${msg}`, context);\n } else {\n console.info(`[fortify] ${msg}`);\n }\n },\n warn: (msg, context) => {\n if (context) {\n console.warn(`[fortify] ${msg}`, context);\n } else {\n console.warn(`[fortify] ${msg}`);\n }\n },\n error: (msg, context) => {\n if (context) {\n console.error(`[fortify] ${msg}`, context);\n } else {\n console.error(`[fortify] ${msg}`);\n }\n },\n};\n\n/**\n * Generic pattern interface that all resilience patterns implement.\n *\n * @template T - The return type of the operation\n */\nexport interface Pattern<T> {\n /**\n * Execute an operation with the pattern's resilience logic.\n *\n * @param operation - The async operation to execute\n * @param signal - Optional AbortSignal for cancellation\n * @returns Promise resolving to the operation result\n */\n execute(operation: Operation<T>, signal?: AbortSignal): Promise<T>;\n}\n\n/**\n * Interface for patterns that support closing/cleanup.\n */\nexport interface Closeable {\n /**\n * Close the pattern and release resources.\n * May wait for in-flight operations to complete.\n */\n close(): Promise<void>;\n}\n\n/**\n * Interface for patterns that support resetting state.\n */\nexport interface Resettable {\n /**\n * Reset the pattern to its initial state.\n */\n reset(): void;\n}\n","import { TimeoutError } from './errors.js';\n\n/**\n * A static never-aborted AbortSignal for use when no signal is provided.\n * Avoids allocating a new AbortController on every operation call.\n *\n * This is a performance optimization for hot paths where signal is optional\n * but the operation signature requires AbortSignal.\n */\nexport const NEVER_ABORTED_SIGNAL: AbortSignal = new AbortController().signal;\n\n/**\n * Sleep for a specified duration with optional cancellation support.\n *\n * @param ms - Duration in milliseconds\n * @param signal - Optional AbortSignal for cancellation\n * @returns Promise that resolves after the delay or rejects if cancelled\n */\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(ensureError(signal.reason));\n return;\n }\n\n const timeoutId = setTimeout(resolve, ms);\n\n signal?.addEventListener(\n 'abort',\n () => {\n clearTimeout(timeoutId);\n reject(ensureError(signal.reason));\n },\n { once: true }\n );\n });\n}\n\n/**\n * Ensure a value is an Error instance.\n * @param reason - The reason to convert to an Error\n * @returns An Error instance\n */\nfunction ensureError(reason: unknown): Error {\n if (reason instanceof Error) {\n return reason;\n }\n if (typeof reason === 'string') {\n return new Error(reason);\n }\n if (reason !== undefined && reason !== null) {\n // Handle objects and other types safely\n try {\n return new Error(JSON.stringify(reason));\n } catch {\n return new Error('Unknown error');\n }\n }\n return new DOMException('Aborted', 'AbortError');\n}\n\n/**\n * Wrap a promise with a timeout.\n *\n * @template T - The promise return type\n * @param promise - The promise to wrap\n * @param ms - Timeout duration in milliseconds\n * @param signal - Optional external AbortSignal for cancellation\n * @returns Promise that resolves with the result or rejects with TimeoutError\n */\nexport async function withTimeout<T>(\n promise: Promise<T>,\n ms: number,\n signal?: AbortSignal\n): Promise<T> {\n // Check if already aborted\n if (signal?.aborted) {\n throw ensureError(signal.reason);\n }\n\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n reject(new TimeoutError(`Operation timed out after ${String(ms)}ms`, ms));\n }, ms);\n });\n\n // Handle external signal\n const abortPromise = signal\n ? new Promise<never>((_, reject) => {\n signal.addEventListener(\n 'abort',\n () => reject(ensureError(signal.reason)),\n { once: true }\n );\n })\n : null;\n\n try {\n const racers: Promise<T>[] = [promise, timeoutPromise];\n if (abortPromise) {\n racers.push(abortPromise);\n }\n return await Promise.race(racers);\n } finally {\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n }\n }\n}\n\n/**\n * Execute an operation with a timeout, passing the combined signal to the operation.\n *\n * @template T - The operation return type\n * @param operation - The operation to execute\n * @param ms - Timeout duration in milliseconds\n * @param signal - Optional external AbortSignal for cancellation\n * @returns Promise that resolves with the result or rejects with TimeoutError\n */\nexport async function executeWithTimeout<T>(\n operation: (signal: AbortSignal) => Promise<T>,\n ms: number,\n signal?: AbortSignal\n): Promise<T> {\n const controller = new AbortController();\n\n // Combine signals\n const combinedSignal = combineSignals(signal, controller.signal);\n\n const timeoutId = setTimeout(() => {\n controller.abort(new TimeoutError(`Operation timed out after ${String(ms)}ms`, ms));\n }, ms);\n\n try {\n return await operation(combinedSignal);\n } finally {\n clearTimeout(timeoutId);\n }\n}\n\n/**\n * Combine multiple AbortSignals into one.\n * Returns a signal that aborts when any of the input signals abort.\n *\n * Uses AbortSignal.any() when available (Node 20+, modern browsers).\n * Falls back to a manual implementation that properly cleans up event listeners.\n *\n * @param signals - AbortSignals to combine (undefined values are filtered out)\n * @returns Combined AbortSignal\n */\nexport function combineSignals(...signals: (AbortSignal | undefined)[]): AbortSignal {\n const validSignals = signals.filter((s): s is AbortSignal => s !== undefined);\n\n if (validSignals.length === 0) {\n return NEVER_ABORTED_SIGNAL;\n }\n\n const firstSignal = validSignals[0];\n if (validSignals.length === 1 && firstSignal) {\n return firstSignal;\n }\n\n // Use AbortSignal.any if available (Node 20+, modern browsers)\n if ('any' in AbortSignal) {\n return AbortSignal.any(validSignals);\n }\n\n // Fallback: create a new controller and link it to all signals\n // Track listeners so we can clean them up to prevent memory leaks\n const controller = new AbortController();\n const listeners: { signal: AbortSignal; listener: () => void }[] = [];\n\n // Cleanup function to remove all listeners\n const cleanup = () => {\n for (const { signal, listener } of listeners) {\n signal.removeEventListener('abort', listener);\n }\n listeners.length = 0;\n };\n\n for (const signal of validSignals) {\n if (signal.aborted) {\n controller.abort(signal.reason);\n return controller.signal;\n }\n\n const listener = () => {\n cleanup(); // Clean up all listeners when any signal aborts\n controller.abort(signal.reason);\n };\n\n listeners.push({ signal, listener });\n signal.addEventListener('abort', listener, { once: true });\n }\n\n return controller.signal;\n}\n\n/**\n * Check if an error is an abort error (from AbortController).\n *\n * @param error - Error to check\n * @returns True if the error is an AbortError\n */\nexport function isAbortError(error: unknown): boolean {\n return (\n error instanceof DOMException &&\n error.name === 'AbortError'\n );\n}\n\n/**\n * Throw if the given signal is aborted.\n * Useful for checking signal state early in operations to avoid unnecessary work.\n *\n * @param signal - AbortSignal to check\n * @throws {DOMException} If signal is aborted (AbortError)\n */\nexport function throwIfAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted) {\n throw signal.reason ?? new DOMException('Aborted', 'AbortError');\n }\n}\n\n/**\n * Safely execute a callback, catching and logging any errors.\n * Used for user-provided callbacks to prevent them from breaking the pattern.\n *\n * @param callback - Callback to execute\n * @param onError - Optional error handler\n */\nexport function safeCallback<T extends (...args: unknown[]) => unknown>(\n callback: T | undefined,\n onError?: (error: Error) => void\n): T | undefined {\n if (!callback) return undefined;\n\n return ((...args: Parameters<T>): ReturnType<T> | undefined => {\n try {\n return callback(...args) as ReturnType<T>;\n } catch (error) {\n if (onError && error instanceof Error) {\n onError(error);\n }\n return undefined;\n }\n }) as T;\n}\n\n/**\n * Jitter mode for delay randomization.\n *\n * - `full`: Random delay from 0 to base delay (aggressive, best for thundering herd)\n * - `equal`: Random delay from 50% to 100% of base delay (balanced)\n * - `decorrelated`: Each sleep is random between base and previous sleep * 3 (AWS-style)\n */\nexport type JitterMode = 'full' | 'equal' | 'decorrelated';\n\n/**\n * Calculate jittered delay to prevent thundering herd.\n *\n * Uses \"equal jitter\" by default: random value between 50-100% of the delay.\n * This provides good dispersion while maintaining reasonable minimum delays.\n *\n * For more aggressive jitter, use mode='full' (0-100% of delay).\n * For AWS-style decorrelated jitter, use mode='decorrelated' with previousDelay.\n *\n * @param delay - Base delay in milliseconds\n * @param mode - Jitter mode: 'full', 'equal', or 'decorrelated' (default: 'equal')\n * @param previousDelay - Previous delay for decorrelated mode (default: delay)\n * @returns Jittered delay\n *\n * @example\n * ```typescript\n * // Equal jitter (default): 50-100% of delay\n * addJitter(1000); // Returns 500-1000ms\n *\n * // Full jitter: 0-100% of delay\n * addJitter(1000, 'full'); // Returns 0-1000ms\n *\n * // Decorrelated jitter (AWS-style)\n * let sleep = initialDelay;\n * sleep = addJitter(baseDelay, 'decorrelated', sleep);\n * ```\n */\nexport function addJitter(\n delay: number,\n mode: JitterMode = 'equal',\n previousDelay?: number\n): number {\n switch (mode) {\n case 'full':\n // Full jitter: random(0, delay)\n return Math.floor(Math.random() * delay);\n\n case 'decorrelated': {\n // Decorrelated jitter: random(base, previous * 3)\n // Capped at delay to prevent unbounded growth\n const prev = previousDelay ?? delay;\n const min = delay;\n const max = Math.min(prev * 3, delay * 10); // Cap at 10x base\n return Math.floor(min + Math.random() * (max - min));\n }\n\n case 'equal':\n default:\n // Equal jitter: delay/2 + random(0, delay/2)\n // Results in 50-100% of original delay\n return Math.floor(delay * 0.5 + Math.random() * delay * 0.5);\n }\n}\n\n/**\n * Clamp a number between min and max values.\n *\n * @param value - Value to clamp\n * @param min - Minimum value\n * @param max - Maximum value\n * @returns Clamped value\n */\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n\n/**\n * Get current timestamp in milliseconds.\n * Uses performance.now() if available for higher precision.\n *\n * @returns Current timestamp in milliseconds\n */\nexport function now(): number {\n if (typeof performance !== 'undefined' && typeof performance.now === 'function') {\n return performance.now();\n }\n return Date.now();\n}\n","import { z } from 'zod';\n\n/**\n * Schema for a function type (Zod 4 compatible).\n * Uses z.function() which validates that the value is a function.\n */\nconst functionSchema = z.function();\n\n/**\n * Base schema for logger configuration.\n * Validates that the logger has the required methods.\n */\nexport const loggerSchema = z.object({\n debug: functionSchema,\n info: functionSchema,\n warn: functionSchema,\n error: functionSchema,\n}).optional();\n\n/**\n * Schema for timeout configuration.\n */\nexport const timeoutConfigSchema = z.object({\n /** Default timeout in milliseconds (default: 30000) */\n defaultTimeout: z.number().int().positive().default(30000),\n /** Callback when timeout occurs */\n onTimeout: functionSchema.optional(),\n /** Logger instance */\n logger: loggerSchema,\n});\n\nexport type TimeoutConfig = z.infer<typeof timeoutConfigSchema>;\n\n/**\n * Schema for retry backoff policy.\n */\nexport const backoffPolicySchema = z.enum(['exponential', 'linear', 'constant']);\n\nexport type BackoffPolicy = z.infer<typeof backoffPolicySchema>;\n\n/**\n * Schema for retry configuration.\n */\nexport const retryConfigSchema = z.object({\n /** Maximum number of attempts including the first (default: 3) */\n maxAttempts: z.number().int().positive().default(3),\n /** Initial delay before first retry in milliseconds (default: 100) */\n initialDelay: z.number().int().positive().default(100),\n /** Maximum delay between retries in milliseconds */\n maxDelay: z.number().int().positive().optional(),\n /** Backoff strategy (default: 'exponential') */\n backoffPolicy: backoffPolicySchema.default('exponential'),\n /** Multiplier for exponential backoff (default: 2.0) */\n multiplier: z.number().positive().default(2.0),\n /** Add random jitter to delays (default: false) */\n jitter: z.boolean().default(false),\n /** Custom function to determine if error is retryable */\n isRetryable: functionSchema.optional(),\n /** Callback on each retry attempt */\n onRetry: functionSchema.optional(),\n /** Logger instance */\n logger: loggerSchema,\n});\n\nexport type RetryConfig = z.infer<typeof retryConfigSchema>;\n\n/**\n * Schema for circuit breaker state.\n */\nexport const circuitBreakerStateSchema = z.enum(['closed', 'open', 'half-open']);\n\nexport type CircuitBreakerState = z.infer<typeof circuitBreakerStateSchema>;\n\n/**\n * Schema for circuit breaker counts/metrics.\n */\nexport const circuitBreakerCountsSchema = z.object({\n requests: z.number().int().nonnegative(),\n totalSuccesses: z.number().int().nonnegative(),\n totalFailures: z.number().int().nonnegative(),\n consecutiveSuccesses: z.number().int().nonnegative(),\n consecutiveFailures: z.number().int().nonnegative(),\n});\n\nexport type CircuitBreakerCounts = z.infer<typeof circuitBreakerCountsSchema>;\n\n/**\n * Schema for circuit breaker configuration.\n */\nexport const circuitBreakerConfigSchema = z.object({\n /** Maximum consecutive failures before opening (default: 5) */\n maxFailures: z.number().int().positive().default(5),\n /** Duration in open state before transitioning to half-open in milliseconds (default: 60000) */\n timeout: z.number().int().positive().default(60000),\n /** Maximum requests allowed in half-open state (default: 1) */\n halfOpenMaxRequests: z.number().int().positive().default(1),\n /** Period to clear counts when closed, 0 means never (default: 0) */\n interval: z.number().int().nonnegative().default(0),\n /** Custom function to determine when to trip the breaker */\n readyToTrip: functionSchema.optional(),\n /** Custom function to determine if result is successful */\n isSuccessful: functionSchema.optional(),\n /** Callback on state change */\n onStateChange: functionSchema.optional(),\n /** Logger instance */\n logger: loggerSchema,\n});\n\nexport type CircuitBreakerConfig = z.infer<typeof circuitBreakerConfigSchema>;\n\n/**\n * Schema for rate limiter configuration.\n */\nexport const rateLimitConfigSchema = z.object({\n /** Number of tokens added per interval (default: 100) */\n rate: z.number().int().positive().default(100),\n /** Maximum bucket capacity (defaults to rate) */\n burst: z.number().int().positive().optional(),\n /** Interval for token refill in milliseconds (default: 1000) */\n interval: z.number().int().positive().default(1000),\n /** Callback when rate limit is hit */\n onLimit: functionSchema.optional(),\n /** Logger instance */\n logger: loggerSchema,\n});\n\nexport type RateLimitConfig = z.infer<typeof rateLimitConfigSchema>;\n\n/**\n * Schema for bulkhead configuration.\n */\nexport const bulkheadConfigSchema = z.object({\n /** Maximum concurrent executions (default: 10) */\n maxConcurrent: z.number().int().positive().default(10),\n /** Maximum queued requests, 0 means no queueing (default: 0) */\n maxQueue: z.number().int().nonnegative().default(0),\n /** Maximum time to wait in queue in milliseconds */\n queueTimeout: z.number().int().positive().optional(),\n /** Callback when request is rejected */\n onRejected: functionSchema.optional(),\n /** Logger instance */\n logger: loggerSchema,\n});\n\nexport type BulkheadConfig = z.infer<typeof bulkheadConfigSchema>;\n\n/**\n * Schema for fallback configuration.\n */\nexport const fallbackConfigSchema = z.object({\n /** Fallback function to execute when primary fails */\n fallback: functionSchema,\n /** Custom function to determine if fallback should be used */\n shouldFallback: functionSchema.optional(),\n /** Callback when fallback is triggered */\n onFallback: functionSchema.optional(),\n /** Callback when primary succeeds */\n onSuccess: functionSchema.optional(),\n /** Logger instance */\n logger: loggerSchema,\n});\n\nexport type FallbackConfig = z.infer<typeof fallbackConfigSchema>;\n","import { z } from 'zod';\n\n/** Maximum reasonable token count (1 billion) */\nconst MAX_TOKENS = 1_000_000_000;\n\n/** Maximum reasonable timestamp (year 2100 in ms) */\nconst MAX_TIMESTAMP = 4_102_444_800_000;\n\n/**\n * Zod schema for validating bucket state from external storage.\n * Use this to validate data retrieved from untrusted storage sources.\n * Includes bounds checking to prevent malicious or corrupted data.\n */\nexport const bucketStateSchema = z.object({\n tokens: z.number().nonnegative().max(MAX_TOKENS),\n lastRefill: z.number().int().nonnegative().max(MAX_TIMESTAMP),\n});\n\n/**\n * State of a rate limit bucket for external storage.\n * Contains all data needed to reconstruct bucket state across invocations.\n */\nexport interface BucketState {\n /** Current number of tokens in the bucket */\n readonly tokens: number;\n /** Timestamp (ms since epoch) of last token refill */\n readonly lastRefill: number;\n}\n\n/**\n * Validates bucket state from external storage.\n * Returns the validated state or null if invalid.\n *\n * @param data - Raw data from storage\n * @returns Validated BucketState or null if invalid\n *\n * @example\n * ```typescript\n * const data = await redis.get(key);\n * const state = validateBucketState(JSON.parse(data));\n * if (state === null) {\n * // Invalid data, treat as new bucket\n * }\n * ```\n */\nexport function validateBucketState(data: unknown): BucketState | null {\n const result = bucketStateSchema.safeParse(data);\n return result.success ? result.data : null;\n}\n\n/**\n * Result of a compare-and-set operation.\n */\nexport interface CompareAndSetResult {\n /** Whether the update was successful */\n readonly success: boolean;\n /** The current state (after operation) */\n readonly currentState: BucketState | null;\n}\n\n/** Maximum key length after sanitization */\nconst MAX_KEY_LENGTH = 256;\n\n/** Pre-compiled regex for control characters (for performance) */\n// eslint-disable-next-line no-control-regex\nconst CONTROL_CHARS_REGEX = /[\\x00-\\x1f\\x7f]/g;\n\n/** Pre-compiled regex for path separators (for performance) */\nconst PATH_SEPARATORS_REGEX = /[/\\\\]/g;\n\n/**\n * Sanitizes a storage key to prevent injection attacks.\n * Removes or encodes potentially dangerous characters.\n *\n * Optimization: Truncates before regex processing to limit work on long strings.\n *\n * @param key - Raw key from user input\n * @returns Sanitized key safe for storage operations\n *\n * @example\n * ```typescript\n * const safeKey = sanitizeStorageKey(userProvidedKey);\n * await storage.get(safeKey);\n * ```\n */\nexport function sanitizeStorageKey(key: string): string {\n // Truncate first to limit regex work on long strings\n const truncated = key.length > MAX_KEY_LENGTH ? key.slice(0, MAX_KEY_LENGTH) : key;\n\n // Replace control characters, null bytes, and path separators\n // Keep alphanumeric, dash, underscore, dot, colon, at\n return truncated\n .replace(CONTROL_CHARS_REGEX, '') // Remove control characters\n .replace(PATH_SEPARATORS_REGEX, '_'); // Replace path separators\n}\n\n/**\n * Storage adapter interface for rate limiting.\n *\n * Implement this interface to persist rate limit state across serverless\n * invocations or distributed systems. The storage adapter enables rate\n * limiting in environments where in-memory state doesn't persist.\n *\n * ## Concurrency Warning\n *\n * **Important:** The basic get/set interface has inherent TOCTOU (time-of-check\n * to time-of-use) race conditions in distributed systems. Between reading the\n * bucket state and writing the updated state, another process may have modified\n * the bucket. This can result in rate limits being slightly over or under the\n * configured limit.\n *\n * For precise rate limiting in high-concurrency distributed environments,\n * implement the optional `compareAndSet` method with atomic operations:\n *\n * - **Redis**: Use Lua scripts with WATCH/MULTI/EXEC\n * - **DynamoDB**: Use conditional writes with version checks\n * - **Forge Storage**: Accepts eventual consistency\n *\n * For most use cases, the slight inaccuracy from race conditions is acceptable.\n * The rate limiter still provides effective protection against abuse.\n *\n * @example Redis implementation with atomic operations\n * ```typescript\n * const redisStorage: RateLimitStorage = {\n * async get(key) {\n * const data = await redis.get(`ratelimit:${key}`);\n * if (!data) return null;\n * return validateBucketState(JSON.parse(data));\n * },\n * async set(key, state, ttlMs) {\n * const value = JSON.stringify(state);\n * if (ttlMs) {\n * await redis.set(`ratelimit:${key}`, value, 'PX', ttlMs);\n * } else {\n * await redis.set(`ratelimit:${key}`, value);\n * }\n * },\n * async compareAndSet(key, expected, newState, ttlMs) {\n * // Use Lua script for atomic compare-and-set\n * const script = `\n * local current = redis.call('GET', KEYS[1])\n * if current == ARGV[1] or (current == false and ARGV[1] == 'null') then\n * if ARGV[3] then\n * redis.call('SET', KEYS[1], ARGV[2], 'PX', ARGV[3])\n * else\n * redis.call('SET', KEYS[1], ARGV[2])\n * end\n * return {1, ARGV[2]}\n * end\n * return {0, current or 'null'}\n * `;\n * const expectedStr = expected ? JSON.stringify(expected) : 'null';\n * const [success, currentStr] = await redis.eval(script, 1,\n * `ratelimit:${key}`, expectedStr, JSON.stringify(newState), ttlMs?.toString()\n * );\n * return {\n * success: success === 1,\n * currentState: currentStr === 'null' ? null : validateBucketState(JSON.parse(currentStr))\n * };\n * },\n * async delete(key) {\n * await redis.del(`ratelimit:${key}`);\n * },\n * async clear() {\n * const keys = await redis.keys('ratelimit:*');\n * if (keys.length > 0) {\n * await redis.del(...keys);\n * }\n * }\n * };\n * ```\n *\n * @example Forge Storage implementation (accepts eventual consistency)\n * ```typescript\n * import { storage } from '@forge/api';\n *\n * const forgeStorage: RateLimitStorage = {\n * async get(key) {\n * const data = await storage.get(`ratelimit:${key}`);\n * return validateBucketState(data);\n * },\n * async set(key, state) {\n * await storage.set(`ratelimit:${key}`, state);\n * },\n * async delete(key) {\n * await storage.delete(`ratelimit:${key}`);\n * }\n * };\n * ```\n */\nexport interface RateLimitStorage {\n /**\n * Retrieve bucket state for a key.\n *\n * @param key - The rate limiting key (already sanitized)\n * @returns The bucket state, or null if not found\n */\n get(key: string): Promise<BucketState | null>;\n\n /**\n * Store bucket state for a key.\n *\n * @param key - The rate limiting key (already sanitized)\n * @param state - The bucket state to store\n * @param ttlMs - Optional TTL in milliseconds for automatic cleanup.\n * Recommended: set to interval * (burst / rate) * 2 to auto-expire\n * stale buckets while keeping active ones alive.\n */\n set(key: string, state: BucketState, ttlMs?: number): Promise<void>;\n\n /**\n * Atomically compare and set bucket state.\n * Optional - implement for precise rate limiting in distributed systems.\n *\n * This method should atomically:\n * 1. Check if the current state matches `expected`\n * 2. If it matches, update to `newState` and return success\n * 3. If it doesn't match, return failure with the current state\n *\n * @param key - The rate limiting key (already sanitized)\n * @param expected - The expected current state (null if expecting no entry)\n * @param newState - The new state to set if expected matches\n * @param ttlMs - Optional TTL in milliseconds\n * @returns Result indicating success and current state\n */\n compareAndSet?(\n key: string,\n expected: BucketState | null,\n newState: BucketState,\n ttlMs?: number\n ): Promise<CompareAndSetResult>;\n\n /**\n * Delete bucket state for a key.\n * Optional - if not implemented, reset operations on individual keys\n * will not be supported.\n *\n * @param key - The rate limiting key (already sanitized)\n */\n delete?(key: string): Promise<void>;\n\n /**\n * Clear all bucket states.\n * Optional - if not implemented, full reset operations will not clear storage.\n */\n clear?(): Promise<void>;\n}\n\n/**\n * In-memory storage implementation using Map.\n * This is the default storage used when no external storage is provided.\n *\n * Features:\n * - LRU eviction when maxEntries is exceeded\n * - Synchronous operations (async interface for compatibility)\n * - No persistence across process restarts\n * - No race conditions (single-threaded JavaScript)\n *\n * @example\n * ```typescript\n * const storage = new MemoryStorage({ maxEntries: 10000 });\n * const limiter = new RateLimiter({ rate: 100, storage });\n * ```\n */\nexport class MemoryStorage implements RateLimitStorage {\n /** Cached resolved promise for void returns to avoid allocation */\n private static readonly RESOLVED_VOID = Promise.resolve();\n\n /** Cached resolved promise for null returns to avoid allocation */\n private static readonly RESOLVED_NULL: Promise<BucketState | null> = Promise.resolve(null);\n\n private readonly entries = new Map<string, BucketState>();\n private readonly maxEntries: number;\n private evictionCount = 0;\n\n /**\n * Create a new in-memory storage.\n *\n * @param options - Storage options\n * @param options.maxEntries - Maximum entries before LRU eviction (0 = unlimited, default: 10000)\n */\n constructor(options?: { readonly maxEntries?: number }) {\n this.maxEntries = options?.maxEntries ?? 10000;\n }\n\n get(key: string): Promise<BucketState | null> {\n const state = this.getSync(key);\n return state === null ? MemoryStorage.RESOLVED_NULL : Promise.resolve(state);\n }\n\n set(key: string, state: BucketState): Promise<void> {\n this.setSync(key, state);\n return MemoryStorage.RESOLVED_VOID;\n }\n\n compareAndSet(\n key: string,\n expected: BucketState | null,\n newState: BucketState\n ): Promise<CompareAndSetResult> {\n const current = this.entries.get(key) ?? null;\n\n // Check if current matches expected\n const matches =\n current === expected ||\n (current !== null &&\n expected !== null &&\n current.tokens === expected.tokens &&\n current.lastRefill === expected.lastRefill);\n\n if (matches) {\n this.setSync(key, newState);\n return Promise.resolve({ success: true, currentState: newState });\n }\n\n return Promise.resolve({ success: false, currentState: current });\n }\n\n delete(key: string): Promise<void> {\n this.deleteSync(key);\n return MemoryStorage.RESOLVED_VOID;\n }\n\n clear(): Promise<void> {\n this.clearSync();\n return MemoryStorage.RESOLVED_VOID;\n }\n\n /**\n * Get the current number of entries.\n */\n size(): number {\n return this.entries.size;\n }\n\n /**\n * Get the total number of evictions since creation.\n */\n getEvictionCount(): number {\n return this.evictionCount;\n }\n\n /**\n * Synchronous get for backward compatibility with sync rate limiter methods.\n * @internal\n */\n getSync(key: string): BucketState | null {\n const state = this.entries.get(key);\n if (state) {\n // LRU touch: move to end\n this.entries.delete(key);\n this.entries.set(key, state);\n }\n return state ?? null;\n }\n\n /**\n * Synchronous set for backward compatibility with sync rate limiter methods.\n * @internal\n */\n setSync(key: string, state: BucketState): void {\n if (this.entries.has(key)) {\n this.entries.delete(key);\n } else if (this.maxEntries > 0 && this.entries.size >= this.maxEntries) {\n const firstKey = this.entries.keys().next().value;\n if (firstKey !== undefined) {\n this.entries.delete(firstKey);\n this.evictionCount++;\n }\n }\n this.entries.set(key, state);\n }\n\n /**\n * Synchronous delete for backward compatibility.\n * @internal\n */\n deleteSync(key: string): void {\n this.entries.delete(key);\n }\n\n /**\n * Synchronous clear for backward compatibility.\n * @internal\n */\n clearSync(): void {\n this.entries.clear();\n }\n}\n"]} |
+1
-1
| { | ||
| "name": "@fortify-ts/core", | ||
| "version": "0.2.0", | ||
| "version": "0.3.0", | ||
| "description": "Core types, errors, and utilities for Fortify TS resilience library", | ||
@@ -5,0 +5,0 @@ "type": "module", |
No README
QualityPackage does not have a README. This may indicate a failed publish or a low quality package.
Found 1 instance in 1 package
205191
11.34%9
12.5%1864
11.02%0
-100%139
Infinity%