flowx-control 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/README.md +443 -0
  2. package/dist/batch.d.mts +37 -0
  3. package/dist/batch.d.ts +37 -0
  4. package/dist/batch.js +75 -0
  5. package/dist/batch.js.map +1 -0
  6. package/dist/batch.mjs +73 -0
  7. package/dist/batch.mjs.map +1 -0
  8. package/dist/bulkhead.d.mts +30 -0
  9. package/dist/bulkhead.d.ts +30 -0
  10. package/dist/bulkhead.js +85 -0
  11. package/dist/bulkhead.js.map +1 -0
  12. package/dist/bulkhead.mjs +83 -0
  13. package/dist/bulkhead.mjs.map +1 -0
  14. package/dist/circuit-breaker.d.mts +44 -0
  15. package/dist/circuit-breaker.d.ts +44 -0
  16. package/dist/circuit-breaker.js +132 -0
  17. package/dist/circuit-breaker.js.map +1 -0
  18. package/dist/circuit-breaker.mjs +130 -0
  19. package/dist/circuit-breaker.mjs.map +1 -0
  20. package/dist/debounce.d.mts +30 -0
  21. package/dist/debounce.d.ts +30 -0
  22. package/dist/debounce.js +96 -0
  23. package/dist/debounce.js.map +1 -0
  24. package/dist/debounce.mjs +94 -0
  25. package/dist/debounce.mjs.map +1 -0
  26. package/dist/deferred.d.mts +27 -0
  27. package/dist/deferred.d.ts +27 -0
  28. package/dist/deferred.js +42 -0
  29. package/dist/deferred.js.map +1 -0
  30. package/dist/deferred.mjs +40 -0
  31. package/dist/deferred.mjs.map +1 -0
  32. package/dist/fallback.d.mts +33 -0
  33. package/dist/fallback.d.ts +33 -0
  34. package/dist/fallback.js +43 -0
  35. package/dist/fallback.js.map +1 -0
  36. package/dist/fallback.mjs +40 -0
  37. package/dist/fallback.mjs.map +1 -0
  38. package/dist/hedge.d.mts +18 -0
  39. package/dist/hedge.d.ts +18 -0
  40. package/dist/hedge.js +47 -0
  41. package/dist/hedge.js.map +1 -0
  42. package/dist/hedge.mjs +45 -0
  43. package/dist/hedge.mjs.map +1 -0
  44. package/dist/index.d.mts +18 -0
  45. package/dist/index.d.ts +18 -0
  46. package/dist/index.js +1151 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/index.mjs +1122 -0
  49. package/dist/index.mjs.map +1 -0
  50. package/dist/memo.d.mts +35 -0
  51. package/dist/memo.d.ts +35 -0
  52. package/dist/memo.js +74 -0
  53. package/dist/memo.js.map +1 -0
  54. package/dist/memo.mjs +72 -0
  55. package/dist/memo.mjs.map +1 -0
  56. package/dist/mutex.d.mts +24 -0
  57. package/dist/mutex.d.ts +24 -0
  58. package/dist/mutex.js +46 -0
  59. package/dist/mutex.js.map +1 -0
  60. package/dist/mutex.mjs +44 -0
  61. package/dist/mutex.mjs.map +1 -0
  62. package/dist/pipeline.d.mts +42 -0
  63. package/dist/pipeline.d.ts +42 -0
  64. package/dist/pipeline.js +30 -0
  65. package/dist/pipeline.js.map +1 -0
  66. package/dist/pipeline.mjs +27 -0
  67. package/dist/pipeline.mjs.map +1 -0
  68. package/dist/poll.d.mts +35 -0
  69. package/dist/poll.d.ts +35 -0
  70. package/dist/poll.js +111 -0
  71. package/dist/poll.js.map +1 -0
  72. package/dist/poll.mjs +109 -0
  73. package/dist/poll.mjs.map +1 -0
  74. package/dist/queue.d.mts +47 -0
  75. package/dist/queue.d.ts +47 -0
  76. package/dist/queue.js +121 -0
  77. package/dist/queue.js.map +1 -0
  78. package/dist/queue.mjs +119 -0
  79. package/dist/queue.mjs.map +1 -0
  80. package/dist/rate-limit.d.mts +28 -0
  81. package/dist/rate-limit.d.ts +28 -0
  82. package/dist/rate-limit.js +94 -0
  83. package/dist/rate-limit.js.map +1 -0
  84. package/dist/rate-limit.mjs +92 -0
  85. package/dist/rate-limit.mjs.map +1 -0
  86. package/dist/retry.d.mts +45 -0
  87. package/dist/retry.d.ts +45 -0
  88. package/dist/retry.js +111 -0
  89. package/dist/retry.js.map +1 -0
  90. package/dist/retry.mjs +108 -0
  91. package/dist/retry.mjs.map +1 -0
  92. package/dist/semaphore.d.mts +27 -0
  93. package/dist/semaphore.d.ts +27 -0
  94. package/dist/semaphore.js +47 -0
  95. package/dist/semaphore.js.map +1 -0
  96. package/dist/semaphore.mjs +45 -0
  97. package/dist/semaphore.mjs.map +1 -0
  98. package/dist/throttle.d.mts +25 -0
  99. package/dist/throttle.d.ts +25 -0
  100. package/dist/throttle.js +97 -0
  101. package/dist/throttle.js.map +1 -0
  102. package/dist/throttle.mjs +95 -0
  103. package/dist/throttle.mjs.map +1 -0
  104. package/dist/timeout.d.mts +19 -0
  105. package/dist/timeout.d.ts +19 -0
  106. package/dist/timeout.js +79 -0
  107. package/dist/timeout.js.map +1 -0
  108. package/dist/timeout.mjs +77 -0
  109. package/dist/timeout.mjs.map +1 -0
  110. package/dist/types-BsCO2J40.d.mts +35 -0
  111. package/dist/types-BsCO2J40.d.ts +35 -0
  112. package/package.json +167 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/batch.ts"],"names":[],"mappings":"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","file":"batch.mjs","sourcesContent":["// ============================================================================\n// FlowX — Types & Error Hierarchy\n// ============================================================================\n\n/** Generic async function signature */\nexport type AsyncFn<TArgs extends any[] = any[], TReturn = any> = (\n ...args: TArgs\n) => Promise<TReturn>;\n\n/** Backoff strategy for retry/poll operations */\nexport type BackoffStrategy =\n | 'fixed'\n | 'linear'\n | 'exponential'\n | ((attempt: number, delay: number) => number);\n\n// ── Error Classes ───────────────────────────────────────────────────────────\n\n/** Base error class for all FlowX errors */\nexport class FlowXError extends Error {\n public readonly code: string;\n constructor(message: string, code: string) {\n super(message);\n this.name = 'FlowXError';\n this.code = code;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a promise exceeds its timeout */\nexport class TimeoutError extends FlowXError {\n constructor(message = 'Operation timed out') {\n super(message, 'ERR_TIMEOUT');\n this.name = 'TimeoutError';\n }\n}\n\n/** Thrown when a circuit breaker is open */\nexport class CircuitBreakerError extends FlowXError {\n constructor(message = 'Circuit breaker is open') {\n super(message, 'ERR_CIRCUIT_OPEN');\n this.name = 'CircuitBreakerError';\n }\n}\n\n/** Thrown when a bulkhead rejects due to capacity */\nexport class BulkheadError extends FlowXError {\n constructor(message = 'Bulkhead capacity exceeded') {\n super(message, 'ERR_BULKHEAD_FULL');\n this.name = 'BulkheadError';\n }\n}\n\n/** Thrown when an operation is aborted */\nexport class AbortError extends FlowXError {\n constructor(message = 'Operation aborted') {\n super(message, 'ERR_ABORTED');\n this.name = 'AbortError';\n }\n}\n\n/** Thrown when rate limit is exceeded */\nexport class RateLimitError extends FlowXError {\n constructor(message = 'Rate limit exceeded') {\n super(message, 'ERR_RATE_LIMIT');\n this.name = 'RateLimitError';\n }\n}\n\n// ── Utility Helpers ─────────────────────────────────────────────────────────\n\n/** Sleep for the specified duration, respecting AbortSignal */\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n if (signal?.aborted) {\n reject(new AbortError());\n return;\n }\n\n let onAbort: (() => void) | undefined;\n\n const timer = setTimeout(() => {\n if (signal && onAbort) {\n signal.removeEventListener('abort', onAbort);\n }\n resolve();\n }, ms);\n\n if (signal) {\n onAbort = () => {\n clearTimeout(timer);\n reject(new AbortError());\n };\n signal.addEventListener('abort', onAbort, { once: true });\n }\n });\n}\n\n/** Calculate delay based on backoff strategy */\nexport function calculateDelay(\n attempt: number,\n baseDelay: number,\n strategy: BackoffStrategy,\n jitter: boolean | number = false,\n): number {\n let delay: number;\n\n if (typeof strategy === 'function') {\n delay = strategy(attempt, baseDelay);\n } else {\n switch (strategy) {\n case 'fixed':\n delay = baseDelay;\n break;\n case 'linear':\n delay = baseDelay * attempt;\n break;\n case 'exponential':\n delay = baseDelay * Math.pow(2, attempt - 1);\n break;\n }\n }\n\n if (jitter) {\n const factor = typeof jitter === 'number' ? jitter : 1;\n delay = delay * (1 - factor * 0.5 + Math.random() * factor);\n }\n\n return Math.max(0, Math.floor(delay));\n}\n","// ============================================================================\n// FlowX — Batch Processing with Concurrency\n// ============================================================================\nimport { AbortError } from './types';\n\nexport interface BatchOptions {\n /** Maximum concurrent batch operations (default: Infinity) */\n concurrency?: number;\n /** Number of items per batch (default: 1) */\n batchSize?: number;\n /** Progress callback */\n onProgress?: (completed: number, total: number) => void;\n /** AbortSignal for cancellation */\n signal?: AbortSignal;\n}\n\nexport interface BatchResult<T> {\n /** All results in order */\n results: T[];\n /** Total items processed */\n total: number;\n /** Number of succeeded items */\n succeeded: number;\n /** Number of failed items */\n failed: number;\n /** Errors indexed by position */\n errors: Map<number, Error>;\n}\n\n/**\n * Process an array of items in batches with concurrency control.\n *\n * @example\n * ```ts\n * const results = await batch(\n * urls,\n * async (url) => fetch(url).then(r => r.json()),\n * { concurrency: 5, batchSize: 10 },\n * );\n * ```\n */\nexport async function batch<TItem, TResult>(\n items: TItem[],\n fn: (item: TItem, index: number) => Promise<TResult>,\n options?: BatchOptions,\n): Promise<BatchResult<TResult>> {\n const { concurrency = Infinity, batchSize = 1, onProgress, signal } = options ?? {};\n\n if (batchSize < 1) throw new RangeError('batchSize must be >= 1');\n if (concurrency < 1) throw new RangeError('concurrency must be >= 1');\n\n const results: TResult[] = new Array(items.length);\n const errors = new Map<number, Error>();\n let completed = 0;\n let succeeded = 0;\n let failed = 0;\n\n // Split items into batches\n const batches: Array<{ items: TItem[]; startIndex: number }> = [];\n for (let i = 0; i < items.length; i += batchSize) {\n batches.push({\n items: items.slice(i, i + batchSize),\n startIndex: i,\n });\n }\n\n // Process batches with concurrency control\n let batchIndex = 0;\n\n async function processBatch(): Promise<void> {\n while (batchIndex < batches.length) {\n if (signal?.aborted) throw new AbortError();\n\n const currentBatch = batches[batchIndex++];\n\n for (let i = 0; i < currentBatch.items.length; i++) {\n if (signal?.aborted) throw new AbortError();\n\n const globalIndex = currentBatch.startIndex + i;\n try {\n results[globalIndex] = await fn(currentBatch.items[i], globalIndex);\n succeeded++;\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n errors.set(globalIndex, err);\n results[globalIndex] = undefined as TResult;\n failed++;\n }\n completed++;\n onProgress?.(completed, items.length);\n }\n }\n }\n\n const workers: Promise<void>[] = [];\n const workerCount = Math.min(concurrency, batches.length);\n\n for (let i = 0; i < workerCount; i++) {\n workers.push(processBatch());\n }\n\n await Promise.all(workers);\n\n return {\n results,\n total: items.length,\n succeeded,\n failed,\n errors,\n };\n}\n"]}
@@ -0,0 +1,30 @@
1
+ interface BulkheadOptions {
2
+ /** Maximum concurrent executions (default: 10) */
3
+ concurrency?: number;
4
+ /** Maximum queue size for waiting tasks (default: 10) */
5
+ queueSize?: number;
6
+ /** Timeout in ms for queued tasks (0 = no timeout) */
7
+ queueTimeout?: number;
8
+ }
9
+ interface Bulkhead {
10
+ /** Execute a function within the bulkhead */
11
+ execute: <T>(fn: () => Promise<T>) => Promise<T>;
12
+ /** Current number of active executions */
13
+ readonly active: number;
14
+ /** Current number of queued tasks */
15
+ readonly queued: number;
16
+ /** Available capacity */
17
+ readonly available: number;
18
+ }
19
+ /**
20
+ * Create a bulkhead to isolate concurrent operations and prevent resource exhaustion.
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * const bulkhead = createBulkhead({ concurrency: 5, queueSize: 10 });
25
+ * const data = await bulkhead.execute(() => fetch('/api'));
26
+ * ```
27
+ */
28
+ declare function createBulkhead(options?: BulkheadOptions): Bulkhead;
29
+
30
+ export { type Bulkhead, type BulkheadOptions, createBulkhead };
@@ -0,0 +1,30 @@
1
+ interface BulkheadOptions {
2
+ /** Maximum concurrent executions (default: 10) */
3
+ concurrency?: number;
4
+ /** Maximum queue size for waiting tasks (default: 10) */
5
+ queueSize?: number;
6
+ /** Timeout in ms for queued tasks (0 = no timeout) */
7
+ queueTimeout?: number;
8
+ }
9
+ interface Bulkhead {
10
+ /** Execute a function within the bulkhead */
11
+ execute: <T>(fn: () => Promise<T>) => Promise<T>;
12
+ /** Current number of active executions */
13
+ readonly active: number;
14
+ /** Current number of queued tasks */
15
+ readonly queued: number;
16
+ /** Available capacity */
17
+ readonly available: number;
18
+ }
19
+ /**
20
+ * Create a bulkhead to isolate concurrent operations and prevent resource exhaustion.
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * const bulkhead = createBulkhead({ concurrency: 5, queueSize: 10 });
25
+ * const data = await bulkhead.execute(() => fetch('/api'));
26
+ * ```
27
+ */
28
+ declare function createBulkhead(options?: BulkheadOptions): Bulkhead;
29
+
30
+ export { type Bulkhead, type BulkheadOptions, createBulkhead };
@@ -0,0 +1,85 @@
1
+ 'use strict';
2
+
3
+ // src/types.ts
4
+ var FlowXError = class extends Error {
5
+ constructor(message, code) {
6
+ super(message);
7
+ this.name = "FlowXError";
8
+ this.code = code;
9
+ Object.setPrototypeOf(this, new.target.prototype);
10
+ }
11
+ };
12
+ var BulkheadError = class extends FlowXError {
13
+ constructor(message = "Bulkhead capacity exceeded") {
14
+ super(message, "ERR_BULKHEAD_FULL");
15
+ this.name = "BulkheadError";
16
+ }
17
+ };
18
+
19
+ // src/bulkhead.ts
20
+ function createBulkhead(options) {
21
+ const { concurrency = 10, queueSize = 10, queueTimeout = 0 } = options ?? {};
22
+ if (concurrency < 1) throw new RangeError("concurrency must be >= 1");
23
+ if (queueSize < 0) throw new RangeError("queueSize must be >= 0");
24
+ let active = 0;
25
+ const queue = [];
26
+ function tryDequeue() {
27
+ while (queue.length > 0 && active < concurrency) {
28
+ const item = queue.shift();
29
+ if (item.timer) clearTimeout(item.timer);
30
+ run(item);
31
+ }
32
+ }
33
+ async function run(item) {
34
+ active++;
35
+ try {
36
+ const value = await item.fn();
37
+ active--;
38
+ tryDequeue();
39
+ item.resolve(value);
40
+ } catch (error) {
41
+ active--;
42
+ tryDequeue();
43
+ item.reject(error instanceof Error ? error : new Error(String(error)));
44
+ }
45
+ }
46
+ function execute(fn) {
47
+ if (active < concurrency) {
48
+ return new Promise((resolve, reject) => {
49
+ run({ fn, resolve, reject });
50
+ });
51
+ }
52
+ if (queue.length >= queueSize) {
53
+ return Promise.reject(new BulkheadError());
54
+ }
55
+ return new Promise((resolve, reject) => {
56
+ const item = { fn, resolve, reject };
57
+ if (queueTimeout > 0) {
58
+ item.timer = setTimeout(() => {
59
+ const idx = queue.indexOf(item);
60
+ if (idx !== -1) {
61
+ queue.splice(idx, 1);
62
+ reject(new BulkheadError("Bulkhead queue timeout exceeded"));
63
+ }
64
+ }, queueTimeout);
65
+ }
66
+ queue.push(item);
67
+ });
68
+ }
69
+ return {
70
+ execute,
71
+ get active() {
72
+ return active;
73
+ },
74
+ get queued() {
75
+ return queue.length;
76
+ },
77
+ get available() {
78
+ return Math.max(0, concurrency - active);
79
+ }
80
+ };
81
+ }
82
+
83
+ exports.createBulkhead = createBulkhead;
84
+ //# sourceMappingURL=bulkhead.js.map
85
+ //# sourceMappingURL=bulkhead.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/bulkhead.ts"],"names":[],"mappings":"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","file":"bulkhead.js","sourcesContent":["// ============================================================================\n// FlowX — Types & Error Hierarchy\n// ============================================================================\n\n/** Generic async function signature */\nexport type AsyncFn<TArgs extends any[] = any[], TReturn = any> = (\n ...args: TArgs\n) => Promise<TReturn>;\n\n/** Backoff strategy for retry/poll operations */\nexport type BackoffStrategy =\n | 'fixed'\n | 'linear'\n | 'exponential'\n | ((attempt: number, delay: number) => number);\n\n// ── Error Classes ───────────────────────────────────────────────────────────\n\n/** Base error class for all FlowX errors */\nexport class FlowXError extends Error {\n public readonly code: string;\n constructor(message: string, code: string) {\n super(message);\n this.name = 'FlowXError';\n this.code = code;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a promise exceeds its timeout */\nexport class TimeoutError extends FlowXError {\n constructor(message = 'Operation timed out') {\n super(message, 'ERR_TIMEOUT');\n this.name = 'TimeoutError';\n }\n}\n\n/** Thrown when a circuit breaker is open */\nexport class CircuitBreakerError extends FlowXError {\n constructor(message = 'Circuit breaker is open') {\n super(message, 'ERR_CIRCUIT_OPEN');\n this.name = 'CircuitBreakerError';\n }\n}\n\n/** Thrown when a bulkhead rejects due to capacity */\nexport class BulkheadError extends FlowXError {\n constructor(message = 'Bulkhead capacity exceeded') {\n super(message, 'ERR_BULKHEAD_FULL');\n this.name = 'BulkheadError';\n }\n}\n\n/** Thrown when an operation is aborted */\nexport class AbortError extends FlowXError {\n constructor(message = 'Operation aborted') {\n super(message, 'ERR_ABORTED');\n this.name = 'AbortError';\n }\n}\n\n/** Thrown when rate limit is exceeded */\nexport class RateLimitError extends FlowXError {\n constructor(message = 'Rate limit exceeded') {\n super(message, 'ERR_RATE_LIMIT');\n this.name = 'RateLimitError';\n }\n}\n\n// ── Utility Helpers ─────────────────────────────────────────────────────────\n\n/** Sleep for the specified duration, respecting AbortSignal */\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n if (signal?.aborted) {\n reject(new AbortError());\n return;\n }\n\n let onAbort: (() => void) | undefined;\n\n const timer = setTimeout(() => {\n if (signal && onAbort) {\n signal.removeEventListener('abort', onAbort);\n }\n resolve();\n }, ms);\n\n if (signal) {\n onAbort = () => {\n clearTimeout(timer);\n reject(new AbortError());\n };\n signal.addEventListener('abort', onAbort, { once: true });\n }\n });\n}\n\n/** Calculate delay based on backoff strategy */\nexport function calculateDelay(\n attempt: number,\n baseDelay: number,\n strategy: BackoffStrategy,\n jitter: boolean | number = false,\n): number {\n let delay: number;\n\n if (typeof strategy === 'function') {\n delay = strategy(attempt, baseDelay);\n } else {\n switch (strategy) {\n case 'fixed':\n delay = baseDelay;\n break;\n case 'linear':\n delay = baseDelay * attempt;\n break;\n case 'exponential':\n delay = baseDelay * Math.pow(2, attempt - 1);\n break;\n }\n }\n\n if (jitter) {\n const factor = typeof jitter === 'number' ? jitter : 1;\n delay = delay * (1 - factor * 0.5 + Math.random() * factor);\n }\n\n return Math.max(0, Math.floor(delay));\n}\n","// ============================================================================\n// FlowX — Bulkhead Isolation Pattern\n// ============================================================================\nimport { BulkheadError } from './types';\n\nexport interface BulkheadOptions {\n /** Maximum concurrent executions (default: 10) */\n concurrency?: number;\n /** Maximum queue size for waiting tasks (default: 10) */\n queueSize?: number;\n /** Timeout in ms for queued tasks (0 = no timeout) */\n queueTimeout?: number;\n}\n\nexport interface Bulkhead {\n /** Execute a function within the bulkhead */\n execute: <T>(fn: () => Promise<T>) => Promise<T>;\n /** Current number of active executions */\n readonly active: number;\n /** Current number of queued tasks */\n readonly queued: number;\n /** Available capacity */\n readonly available: number;\n}\n\ninterface QueueItem<T = any> {\n fn: () => Promise<T>;\n resolve: (value: T) => void;\n reject: (error: Error) => void;\n timer?: ReturnType<typeof setTimeout>;\n}\n\n/**\n * Create a bulkhead to isolate concurrent operations and prevent resource exhaustion.\n *\n * @example\n * ```ts\n * const bulkhead = createBulkhead({ concurrency: 5, queueSize: 10 });\n * const data = await bulkhead.execute(() => fetch('/api'));\n * ```\n */\nexport function createBulkhead(options?: BulkheadOptions): Bulkhead {\n const { concurrency = 10, queueSize = 10, queueTimeout = 0 } = options ?? {};\n\n if (concurrency < 1) throw new RangeError('concurrency must be >= 1');\n if (queueSize < 0) throw new RangeError('queueSize must be >= 0');\n\n let active = 0;\n const queue: QueueItem[] = [];\n\n function tryDequeue(): void {\n while (queue.length > 0 && active < concurrency) {\n const item = queue.shift()!;\n if (item.timer) clearTimeout(item.timer);\n run(item);\n }\n }\n\n async function run<T>(item: QueueItem<T>): Promise<void> {\n active++;\n try {\n const value = await item.fn();\n active--;\n tryDequeue();\n item.resolve(value);\n } catch (error) {\n active--;\n tryDequeue();\n item.reject(error instanceof Error ? error : new Error(String(error)));\n }\n }\n\n function execute<T>(fn: () => Promise<T>): Promise<T> {\n if (active < concurrency) {\n return new Promise<T>((resolve, reject) => {\n run({ fn, resolve, reject });\n });\n }\n\n if (queue.length >= queueSize) {\n return Promise.reject(new BulkheadError());\n }\n\n return new Promise<T>((resolve, reject) => {\n const item: QueueItem<T> = { fn, resolve, reject };\n\n if (queueTimeout > 0) {\n item.timer = setTimeout(() => {\n const idx = queue.indexOf(item as QueueItem);\n if (idx !== -1) {\n queue.splice(idx, 1);\n reject(new BulkheadError('Bulkhead queue timeout exceeded'));\n }\n }, queueTimeout);\n }\n\n queue.push(item as QueueItem);\n });\n }\n\n return {\n execute,\n get active() {\n return active;\n },\n get queued() {\n return queue.length;\n },\n get available() {\n return Math.max(0, concurrency - active);\n },\n };\n}\n"]}
@@ -0,0 +1,83 @@
1
+ // src/types.ts
2
+ var FlowXError = class extends Error {
3
+ constructor(message, code) {
4
+ super(message);
5
+ this.name = "FlowXError";
6
+ this.code = code;
7
+ Object.setPrototypeOf(this, new.target.prototype);
8
+ }
9
+ };
10
+ var BulkheadError = class extends FlowXError {
11
+ constructor(message = "Bulkhead capacity exceeded") {
12
+ super(message, "ERR_BULKHEAD_FULL");
13
+ this.name = "BulkheadError";
14
+ }
15
+ };
16
+
17
+ // src/bulkhead.ts
18
+ function createBulkhead(options) {
19
+ const { concurrency = 10, queueSize = 10, queueTimeout = 0 } = options ?? {};
20
+ if (concurrency < 1) throw new RangeError("concurrency must be >= 1");
21
+ if (queueSize < 0) throw new RangeError("queueSize must be >= 0");
22
+ let active = 0;
23
+ const queue = [];
24
+ function tryDequeue() {
25
+ while (queue.length > 0 && active < concurrency) {
26
+ const item = queue.shift();
27
+ if (item.timer) clearTimeout(item.timer);
28
+ run(item);
29
+ }
30
+ }
31
+ async function run(item) {
32
+ active++;
33
+ try {
34
+ const value = await item.fn();
35
+ active--;
36
+ tryDequeue();
37
+ item.resolve(value);
38
+ } catch (error) {
39
+ active--;
40
+ tryDequeue();
41
+ item.reject(error instanceof Error ? error : new Error(String(error)));
42
+ }
43
+ }
44
+ function execute(fn) {
45
+ if (active < concurrency) {
46
+ return new Promise((resolve, reject) => {
47
+ run({ fn, resolve, reject });
48
+ });
49
+ }
50
+ if (queue.length >= queueSize) {
51
+ return Promise.reject(new BulkheadError());
52
+ }
53
+ return new Promise((resolve, reject) => {
54
+ const item = { fn, resolve, reject };
55
+ if (queueTimeout > 0) {
56
+ item.timer = setTimeout(() => {
57
+ const idx = queue.indexOf(item);
58
+ if (idx !== -1) {
59
+ queue.splice(idx, 1);
60
+ reject(new BulkheadError("Bulkhead queue timeout exceeded"));
61
+ }
62
+ }, queueTimeout);
63
+ }
64
+ queue.push(item);
65
+ });
66
+ }
67
+ return {
68
+ execute,
69
+ get active() {
70
+ return active;
71
+ },
72
+ get queued() {
73
+ return queue.length;
74
+ },
75
+ get available() {
76
+ return Math.max(0, concurrency - active);
77
+ }
78
+ };
79
+ }
80
+
81
+ export { createBulkhead };
82
+ //# sourceMappingURL=bulkhead.mjs.map
83
+ //# sourceMappingURL=bulkhead.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/bulkhead.ts"],"names":[],"mappings":"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","file":"bulkhead.mjs","sourcesContent":["// ============================================================================\n// FlowX — Types & Error Hierarchy\n// ============================================================================\n\n/** Generic async function signature */\nexport type AsyncFn<TArgs extends any[] = any[], TReturn = any> = (\n ...args: TArgs\n) => Promise<TReturn>;\n\n/** Backoff strategy for retry/poll operations */\nexport type BackoffStrategy =\n | 'fixed'\n | 'linear'\n | 'exponential'\n | ((attempt: number, delay: number) => number);\n\n// ── Error Classes ───────────────────────────────────────────────────────────\n\n/** Base error class for all FlowX errors */\nexport class FlowXError extends Error {\n public readonly code: string;\n constructor(message: string, code: string) {\n super(message);\n this.name = 'FlowXError';\n this.code = code;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a promise exceeds its timeout */\nexport class TimeoutError extends FlowXError {\n constructor(message = 'Operation timed out') {\n super(message, 'ERR_TIMEOUT');\n this.name = 'TimeoutError';\n }\n}\n\n/** Thrown when a circuit breaker is open */\nexport class CircuitBreakerError extends FlowXError {\n constructor(message = 'Circuit breaker is open') {\n super(message, 'ERR_CIRCUIT_OPEN');\n this.name = 'CircuitBreakerError';\n }\n}\n\n/** Thrown when a bulkhead rejects due to capacity */\nexport class BulkheadError extends FlowXError {\n constructor(message = 'Bulkhead capacity exceeded') {\n super(message, 'ERR_BULKHEAD_FULL');\n this.name = 'BulkheadError';\n }\n}\n\n/** Thrown when an operation is aborted */\nexport class AbortError extends FlowXError {\n constructor(message = 'Operation aborted') {\n super(message, 'ERR_ABORTED');\n this.name = 'AbortError';\n }\n}\n\n/** Thrown when rate limit is exceeded */\nexport class RateLimitError extends FlowXError {\n constructor(message = 'Rate limit exceeded') {\n super(message, 'ERR_RATE_LIMIT');\n this.name = 'RateLimitError';\n }\n}\n\n// ── Utility Helpers ─────────────────────────────────────────────────────────\n\n/** Sleep for the specified duration, respecting AbortSignal */\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n if (signal?.aborted) {\n reject(new AbortError());\n return;\n }\n\n let onAbort: (() => void) | undefined;\n\n const timer = setTimeout(() => {\n if (signal && onAbort) {\n signal.removeEventListener('abort', onAbort);\n }\n resolve();\n }, ms);\n\n if (signal) {\n onAbort = () => {\n clearTimeout(timer);\n reject(new AbortError());\n };\n signal.addEventListener('abort', onAbort, { once: true });\n }\n });\n}\n\n/** Calculate delay based on backoff strategy */\nexport function calculateDelay(\n attempt: number,\n baseDelay: number,\n strategy: BackoffStrategy,\n jitter: boolean | number = false,\n): number {\n let delay: number;\n\n if (typeof strategy === 'function') {\n delay = strategy(attempt, baseDelay);\n } else {\n switch (strategy) {\n case 'fixed':\n delay = baseDelay;\n break;\n case 'linear':\n delay = baseDelay * attempt;\n break;\n case 'exponential':\n delay = baseDelay * Math.pow(2, attempt - 1);\n break;\n }\n }\n\n if (jitter) {\n const factor = typeof jitter === 'number' ? jitter : 1;\n delay = delay * (1 - factor * 0.5 + Math.random() * factor);\n }\n\n return Math.max(0, Math.floor(delay));\n}\n","// ============================================================================\n// FlowX — Bulkhead Isolation Pattern\n// ============================================================================\nimport { BulkheadError } from './types';\n\nexport interface BulkheadOptions {\n /** Maximum concurrent executions (default: 10) */\n concurrency?: number;\n /** Maximum queue size for waiting tasks (default: 10) */\n queueSize?: number;\n /** Timeout in ms for queued tasks (0 = no timeout) */\n queueTimeout?: number;\n}\n\nexport interface Bulkhead {\n /** Execute a function within the bulkhead */\n execute: <T>(fn: () => Promise<T>) => Promise<T>;\n /** Current number of active executions */\n readonly active: number;\n /** Current number of queued tasks */\n readonly queued: number;\n /** Available capacity */\n readonly available: number;\n}\n\ninterface QueueItem<T = any> {\n fn: () => Promise<T>;\n resolve: (value: T) => void;\n reject: (error: Error) => void;\n timer?: ReturnType<typeof setTimeout>;\n}\n\n/**\n * Create a bulkhead to isolate concurrent operations and prevent resource exhaustion.\n *\n * @example\n * ```ts\n * const bulkhead = createBulkhead({ concurrency: 5, queueSize: 10 });\n * const data = await bulkhead.execute(() => fetch('/api'));\n * ```\n */\nexport function createBulkhead(options?: BulkheadOptions): Bulkhead {\n const { concurrency = 10, queueSize = 10, queueTimeout = 0 } = options ?? {};\n\n if (concurrency < 1) throw new RangeError('concurrency must be >= 1');\n if (queueSize < 0) throw new RangeError('queueSize must be >= 0');\n\n let active = 0;\n const queue: QueueItem[] = [];\n\n function tryDequeue(): void {\n while (queue.length > 0 && active < concurrency) {\n const item = queue.shift()!;\n if (item.timer) clearTimeout(item.timer);\n run(item);\n }\n }\n\n async function run<T>(item: QueueItem<T>): Promise<void> {\n active++;\n try {\n const value = await item.fn();\n active--;\n tryDequeue();\n item.resolve(value);\n } catch (error) {\n active--;\n tryDequeue();\n item.reject(error instanceof Error ? error : new Error(String(error)));\n }\n }\n\n function execute<T>(fn: () => Promise<T>): Promise<T> {\n if (active < concurrency) {\n return new Promise<T>((resolve, reject) => {\n run({ fn, resolve, reject });\n });\n }\n\n if (queue.length >= queueSize) {\n return Promise.reject(new BulkheadError());\n }\n\n return new Promise<T>((resolve, reject) => {\n const item: QueueItem<T> = { fn, resolve, reject };\n\n if (queueTimeout > 0) {\n item.timer = setTimeout(() => {\n const idx = queue.indexOf(item as QueueItem);\n if (idx !== -1) {\n queue.splice(idx, 1);\n reject(new BulkheadError('Bulkhead queue timeout exceeded'));\n }\n }, queueTimeout);\n }\n\n queue.push(item as QueueItem);\n });\n }\n\n return {\n execute,\n get active() {\n return active;\n },\n get queued() {\n return queue.length;\n },\n get available() {\n return Math.max(0, concurrency - active);\n },\n };\n}\n"]}
@@ -0,0 +1,44 @@
1
+ type CircuitState = 'closed' | 'open' | 'half-open';
2
+ interface CircuitBreakerOptions {
3
+ /** Number of failures before opening the circuit (default: 5) */
4
+ failureThreshold?: number;
5
+ /** Time in ms before attempting half-open (default: 30000) */
6
+ resetTimeout?: number;
7
+ /** Max concurrent calls in half-open state (default: 1) */
8
+ halfOpenLimit?: number;
9
+ /** Custom predicate to decide if an error should count as a failure */
10
+ shouldTrip?: (error: Error) => boolean;
11
+ /** Callback fired on state changes */
12
+ onStateChange?: (from: CircuitState, to: CircuitState) => void;
13
+ /** Number of successes in half-open to close the circuit (default: 1) */
14
+ successThreshold?: number;
15
+ }
16
+ interface CircuitBreaker<TArgs extends any[], TReturn> {
17
+ /** Execute the protected function */
18
+ fire: (...args: TArgs) => Promise<TReturn>;
19
+ /** Get the current circuit state */
20
+ readonly state: CircuitState;
21
+ /** Get the current failure count */
22
+ readonly failureCount: number;
23
+ /** Get the current success count (in half-open) */
24
+ readonly successCount: number;
25
+ /** Manually reset the circuit to closed */
26
+ reset: () => void;
27
+ /** Manually open the circuit */
28
+ open: () => void;
29
+ }
30
+ /**
31
+ * Create a circuit breaker to protect against cascading failures.
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * const breaker = createCircuitBreaker(callExternalApi, {
36
+ * failureThreshold: 5,
37
+ * resetTimeout: 30000,
38
+ * });
39
+ * const data = await breaker.fire('arg1');
40
+ * ```
41
+ */
42
+ declare function createCircuitBreaker<TArgs extends any[], TReturn>(fn: (...args: TArgs) => Promise<TReturn>, options?: CircuitBreakerOptions): CircuitBreaker<TArgs, TReturn>;
43
+
44
+ export { type CircuitBreaker, type CircuitBreakerOptions, type CircuitState, createCircuitBreaker };
@@ -0,0 +1,44 @@
1
+ type CircuitState = 'closed' | 'open' | 'half-open';
2
+ interface CircuitBreakerOptions {
3
+ /** Number of failures before opening the circuit (default: 5) */
4
+ failureThreshold?: number;
5
+ /** Time in ms before attempting half-open (default: 30000) */
6
+ resetTimeout?: number;
7
+ /** Max concurrent calls in half-open state (default: 1) */
8
+ halfOpenLimit?: number;
9
+ /** Custom predicate to decide if an error should count as a failure */
10
+ shouldTrip?: (error: Error) => boolean;
11
+ /** Callback fired on state changes */
12
+ onStateChange?: (from: CircuitState, to: CircuitState) => void;
13
+ /** Number of successes in half-open to close the circuit (default: 1) */
14
+ successThreshold?: number;
15
+ }
16
+ interface CircuitBreaker<TArgs extends any[], TReturn> {
17
+ /** Execute the protected function */
18
+ fire: (...args: TArgs) => Promise<TReturn>;
19
+ /** Get the current circuit state */
20
+ readonly state: CircuitState;
21
+ /** Get the current failure count */
22
+ readonly failureCount: number;
23
+ /** Get the current success count (in half-open) */
24
+ readonly successCount: number;
25
+ /** Manually reset the circuit to closed */
26
+ reset: () => void;
27
+ /** Manually open the circuit */
28
+ open: () => void;
29
+ }
30
+ /**
31
+ * Create a circuit breaker to protect against cascading failures.
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * const breaker = createCircuitBreaker(callExternalApi, {
36
+ * failureThreshold: 5,
37
+ * resetTimeout: 30000,
38
+ * });
39
+ * const data = await breaker.fire('arg1');
40
+ * ```
41
+ */
42
+ declare function createCircuitBreaker<TArgs extends any[], TReturn>(fn: (...args: TArgs) => Promise<TReturn>, options?: CircuitBreakerOptions): CircuitBreaker<TArgs, TReturn>;
43
+
44
+ export { type CircuitBreaker, type CircuitBreakerOptions, type CircuitState, createCircuitBreaker };
@@ -0,0 +1,132 @@
1
+ 'use strict';
2
+
3
+ // src/types.ts
4
+ var FlowXError = class extends Error {
5
+ constructor(message, code) {
6
+ super(message);
7
+ this.name = "FlowXError";
8
+ this.code = code;
9
+ Object.setPrototypeOf(this, new.target.prototype);
10
+ }
11
+ };
12
+ var CircuitBreakerError = class extends FlowXError {
13
+ constructor(message = "Circuit breaker is open") {
14
+ super(message, "ERR_CIRCUIT_OPEN");
15
+ this.name = "CircuitBreakerError";
16
+ }
17
+ };
18
+
19
+ // src/circuit-breaker.ts
20
+ function createCircuitBreaker(fn, options) {
21
+ const {
22
+ failureThreshold = 5,
23
+ resetTimeout = 3e4,
24
+ halfOpenLimit = 1,
25
+ shouldTrip,
26
+ onStateChange,
27
+ successThreshold = 1
28
+ } = options ?? {};
29
+ let state = "closed";
30
+ let failureCount = 0;
31
+ let successCount = 0;
32
+ let halfOpenActive = 0;
33
+ let resetTimer = null;
34
+ function transition(to) {
35
+ if (state === to) return;
36
+ const from = state;
37
+ state = to;
38
+ onStateChange?.(from, to);
39
+ }
40
+ function scheduleReset() {
41
+ if (resetTimer) clearTimeout(resetTimer);
42
+ resetTimer = setTimeout(() => {
43
+ resetTimer = null;
44
+ transition("half-open");
45
+ halfOpenActive = 0;
46
+ successCount = 0;
47
+ }, resetTimeout);
48
+ }
49
+ function reset() {
50
+ if (resetTimer) {
51
+ clearTimeout(resetTimer);
52
+ resetTimer = null;
53
+ }
54
+ failureCount = 0;
55
+ successCount = 0;
56
+ halfOpenActive = 0;
57
+ transition("closed");
58
+ }
59
+ function manualOpen() {
60
+ if (resetTimer) {
61
+ clearTimeout(resetTimer);
62
+ resetTimer = null;
63
+ }
64
+ failureCount = 0;
65
+ successCount = 0;
66
+ halfOpenActive = 0;
67
+ transition("open");
68
+ scheduleReset();
69
+ }
70
+ async function fire(...args) {
71
+ if (state === "open") {
72
+ throw new CircuitBreakerError();
73
+ }
74
+ if (state === "half-open" && halfOpenActive >= halfOpenLimit) {
75
+ throw new CircuitBreakerError("Circuit breaker is half-open \u2014 limit reached");
76
+ }
77
+ if (state === "half-open") {
78
+ halfOpenActive++;
79
+ }
80
+ try {
81
+ const result = await fn(...args);
82
+ if (state === "half-open") {
83
+ successCount++;
84
+ halfOpenActive--;
85
+ if (successCount >= successThreshold) {
86
+ reset();
87
+ }
88
+ } else {
89
+ failureCount = 0;
90
+ }
91
+ return result;
92
+ } catch (error) {
93
+ const err = error instanceof Error ? error : new Error(String(error));
94
+ if (shouldTrip && !shouldTrip(err)) {
95
+ if (state === "half-open") {
96
+ halfOpenActive--;
97
+ }
98
+ throw err;
99
+ }
100
+ if (state === "half-open") {
101
+ halfOpenActive--;
102
+ transition("open");
103
+ scheduleReset();
104
+ } else {
105
+ failureCount++;
106
+ if (failureCount >= failureThreshold) {
107
+ transition("open");
108
+ scheduleReset();
109
+ }
110
+ }
111
+ throw err;
112
+ }
113
+ }
114
+ return {
115
+ fire,
116
+ get state() {
117
+ return state;
118
+ },
119
+ get failureCount() {
120
+ return failureCount;
121
+ },
122
+ get successCount() {
123
+ return successCount;
124
+ },
125
+ reset,
126
+ open: manualOpen
127
+ };
128
+ }
129
+
130
+ exports.createCircuitBreaker = createCircuitBreaker;
131
+ //# sourceMappingURL=circuit-breaker.js.map
132
+ //# sourceMappingURL=circuit-breaker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/circuit-breaker.ts"],"names":[],"mappings":"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","file":"circuit-breaker.js","sourcesContent":["// ============================================================================\n// FlowX — Types & Error Hierarchy\n// ============================================================================\n\n/** Generic async function signature */\nexport type AsyncFn<TArgs extends any[] = any[], TReturn = any> = (\n ...args: TArgs\n) => Promise<TReturn>;\n\n/** Backoff strategy for retry/poll operations */\nexport type BackoffStrategy =\n | 'fixed'\n | 'linear'\n | 'exponential'\n | ((attempt: number, delay: number) => number);\n\n// ── Error Classes ───────────────────────────────────────────────────────────\n\n/** Base error class for all FlowX errors */\nexport class FlowXError extends Error {\n public readonly code: string;\n constructor(message: string, code: string) {\n super(message);\n this.name = 'FlowXError';\n this.code = code;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a promise exceeds its timeout */\nexport class TimeoutError extends FlowXError {\n constructor(message = 'Operation timed out') {\n super(message, 'ERR_TIMEOUT');\n this.name = 'TimeoutError';\n }\n}\n\n/** Thrown when a circuit breaker is open */\nexport class CircuitBreakerError extends FlowXError {\n constructor(message = 'Circuit breaker is open') {\n super(message, 'ERR_CIRCUIT_OPEN');\n this.name = 'CircuitBreakerError';\n }\n}\n\n/** Thrown when a bulkhead rejects due to capacity */\nexport class BulkheadError extends FlowXError {\n constructor(message = 'Bulkhead capacity exceeded') {\n super(message, 'ERR_BULKHEAD_FULL');\n this.name = 'BulkheadError';\n }\n}\n\n/** Thrown when an operation is aborted */\nexport class AbortError extends FlowXError {\n constructor(message = 'Operation aborted') {\n super(message, 'ERR_ABORTED');\n this.name = 'AbortError';\n }\n}\n\n/** Thrown when rate limit is exceeded */\nexport class RateLimitError extends FlowXError {\n constructor(message = 'Rate limit exceeded') {\n super(message, 'ERR_RATE_LIMIT');\n this.name = 'RateLimitError';\n }\n}\n\n// ── Utility Helpers ─────────────────────────────────────────────────────────\n\n/** Sleep for the specified duration, respecting AbortSignal */\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n if (signal?.aborted) {\n reject(new AbortError());\n return;\n }\n\n let onAbort: (() => void) | undefined;\n\n const timer = setTimeout(() => {\n if (signal && onAbort) {\n signal.removeEventListener('abort', onAbort);\n }\n resolve();\n }, ms);\n\n if (signal) {\n onAbort = () => {\n clearTimeout(timer);\n reject(new AbortError());\n };\n signal.addEventListener('abort', onAbort, { once: true });\n }\n });\n}\n\n/** Calculate delay based on backoff strategy */\nexport function calculateDelay(\n attempt: number,\n baseDelay: number,\n strategy: BackoffStrategy,\n jitter: boolean | number = false,\n): number {\n let delay: number;\n\n if (typeof strategy === 'function') {\n delay = strategy(attempt, baseDelay);\n } else {\n switch (strategy) {\n case 'fixed':\n delay = baseDelay;\n break;\n case 'linear':\n delay = baseDelay * attempt;\n break;\n case 'exponential':\n delay = baseDelay * Math.pow(2, attempt - 1);\n break;\n }\n }\n\n if (jitter) {\n const factor = typeof jitter === 'number' ? jitter : 1;\n delay = delay * (1 - factor * 0.5 + Math.random() * factor);\n }\n\n return Math.max(0, Math.floor(delay));\n}\n","// ============================================================================\n// FlowX — Circuit Breaker Pattern\n// ============================================================================\nimport { CircuitBreakerError } from './types';\n\nexport type CircuitState = 'closed' | 'open' | 'half-open';\n\nexport interface CircuitBreakerOptions {\n /** Number of failures before opening the circuit (default: 5) */\n failureThreshold?: number;\n /** Time in ms before attempting half-open (default: 30000) */\n resetTimeout?: number;\n /** Max concurrent calls in half-open state (default: 1) */\n halfOpenLimit?: number;\n /** Custom predicate to decide if an error should count as a failure */\n shouldTrip?: (error: Error) => boolean;\n /** Callback fired on state changes */\n onStateChange?: (from: CircuitState, to: CircuitState) => void;\n /** Number of successes in half-open to close the circuit (default: 1) */\n successThreshold?: number;\n}\n\nexport interface CircuitBreaker<TArgs extends any[], TReturn> {\n /** Execute the protected function */\n fire: (...args: TArgs) => Promise<TReturn>;\n /** Get the current circuit state */\n readonly state: CircuitState;\n /** Get the current failure count */\n readonly failureCount: number;\n /** Get the current success count (in half-open) */\n readonly successCount: number;\n /** Manually reset the circuit to closed */\n reset: () => void;\n /** Manually open the circuit */\n open: () => void;\n}\n\n/**\n * Create a circuit breaker to protect against cascading failures.\n *\n * @example\n * ```ts\n * const breaker = createCircuitBreaker(callExternalApi, {\n * failureThreshold: 5,\n * resetTimeout: 30000,\n * });\n * const data = await breaker.fire('arg1');\n * ```\n */\nexport function createCircuitBreaker<TArgs extends any[], TReturn>(\n fn: (...args: TArgs) => Promise<TReturn>,\n options?: CircuitBreakerOptions,\n): CircuitBreaker<TArgs, TReturn> {\n const {\n failureThreshold = 5,\n resetTimeout = 30000,\n halfOpenLimit = 1,\n shouldTrip,\n onStateChange,\n successThreshold = 1,\n } = options ?? {};\n\n let state: CircuitState = 'closed';\n let failureCount = 0;\n let successCount = 0;\n let halfOpenActive = 0;\n let resetTimer: ReturnType<typeof setTimeout> | null = null;\n\n function transition(to: CircuitState): void {\n if (state === to) return;\n const from = state;\n state = to;\n onStateChange?.(from, to);\n }\n\n function scheduleReset(): void {\n /* istanbul ignore next -- defensive guard; timer always fires before reschedule in normal flow */\n if (resetTimer) clearTimeout(resetTimer);\n resetTimer = setTimeout(() => {\n resetTimer = null;\n transition('half-open');\n halfOpenActive = 0;\n successCount = 0;\n }, resetTimeout);\n }\n\n function reset(): void {\n if (resetTimer) {\n clearTimeout(resetTimer);\n resetTimer = null;\n }\n failureCount = 0;\n successCount = 0;\n halfOpenActive = 0;\n transition('closed');\n }\n\n function manualOpen(): void {\n if (resetTimer) {\n clearTimeout(resetTimer);\n resetTimer = null;\n }\n failureCount = 0;\n successCount = 0;\n halfOpenActive = 0;\n transition('open');\n scheduleReset();\n }\n\n async function fire(...args: TArgs): Promise<TReturn> {\n if (state === 'open') {\n throw new CircuitBreakerError();\n }\n\n if (state === 'half-open' && halfOpenActive >= halfOpenLimit) {\n throw new CircuitBreakerError('Circuit breaker is half-open — limit reached');\n }\n\n if (state === 'half-open') {\n halfOpenActive++;\n }\n\n try {\n const result = await fn(...args);\n\n if (state === 'half-open') {\n successCount++;\n halfOpenActive--;\n if (successCount >= successThreshold) {\n reset();\n }\n } else {\n // In closed state, reset failure count on success\n failureCount = 0;\n }\n\n return result;\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n\n if (shouldTrip && !shouldTrip(err)) {\n if (state === 'half-open') {\n halfOpenActive--;\n }\n throw err;\n }\n\n if (state === 'half-open') {\n halfOpenActive--;\n transition('open');\n scheduleReset();\n } else {\n failureCount++;\n if (failureCount >= failureThreshold) {\n transition('open');\n scheduleReset();\n }\n }\n\n throw err;\n }\n }\n\n return {\n fire,\n get state() {\n return state;\n },\n get failureCount() {\n return failureCount;\n },\n get successCount() {\n return successCount;\n },\n reset,\n open: manualOpen,\n };\n}\n"]}