@git-stunts/alfred 0.2.0 → 0.3.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.
package/README.md CHANGED
@@ -1,20 +1,43 @@
1
1
  # @git-stunts/alfred
2
2
 
3
- > *"Why do we fall, Bruce?"*
3
+ ```text
4
+ .o. oooo .o88o. .o8
5
+ .888. `888 888 `" "888
6
+ .8"888. 888 o888oo oooo d8b .ooooo. .oooo888
7
+ .8' `888. 888 888 `888""8P d88' `88b d88' `888
8
+ .88ooo8888. 888 888 888 888ooo888 888 888
9
+ .8' `888. 888 888 888 888 .o 888 888
10
+ o88o o8888o o888o o888o d888b `Y8bod8P' `Y8bod88P"
11
+ ```
12
+
13
+ [![JSR](https://jsr.io/badges/@git-stunts/alfred)](https://jsr.io/@git-stunts/alfred)
14
+ [![NPM Version](https://img.shields.io/npm/v/@git-stunts/alfred)](https://www.npmjs.com/package/@git-stunts/alfred)
15
+ [![CI](https://github.com/git-stunts/alfred/actions/workflows/ci.yml/badge.svg)](https://github.com/git-stunts/alfred/actions/workflows/ci.yml)
16
+
17
+ > _"Why do we fall, Bruce?"_
4
18
  >
5
- > *"So we can `retry({ backoff: 'exponential', jitter: 'decorrelated' })`."*
19
+ > _"So we can `retry({ backoff: 'exponential', jitter: 'decorrelated' })`."_
6
20
 
7
- Resilience patterns for async operations. *Tuff 'nuff for most stuff!*
21
+ Resilience patterns for async operations. _Tuff 'nuff for most stuff!_
8
22
 
9
23
  ## Installation
10
24
 
25
+ ### NPM
26
+
11
27
  ```bash
12
28
  npm install @git-stunts/alfred
13
29
  ```
14
30
 
31
+ ### JSR (Deno, Bun, Node)
32
+
33
+ ```bash
34
+ npx jsr add @git-stunts/alfred
35
+ ```
36
+
15
37
  ## Multi-Runtime Support
16
38
 
17
39
  Alfred is designed to be platform-agnostic and is tested against:
40
+
18
41
  - **Node.js** (>= 20.0.0)
19
42
  - **Bun** (>= 1.0.0)
20
43
  - **Deno** (>= 1.35.0)
@@ -27,10 +50,11 @@ It uses standard Web APIs (AbortController, AbortSignal) and provides runtime-aw
27
50
  import { retry, circuitBreaker, timeout, compose } from '@git-stunts/alfred';
28
51
 
29
52
  // Simple retry with exponential backoff
30
- const data = await retry(
31
- () => fetch('https://api.example.com/data'),
32
- { retries: 3, backoff: 'exponential', delay: 100 }
33
- );
53
+ const data = await retry(() => fetch('https://api.example.com/data'), {
54
+ retries: 3,
55
+ backoff: 'exponential',
56
+ delay: 100,
57
+ });
34
58
 
35
59
  // Circuit breaker - fail fast when service is down
36
60
  const breaker = circuitBreaker({ threshold: 5, duration: 60000 });
@@ -64,13 +88,13 @@ await retry(() => mightFail(), {
64
88
  retries: 5,
65
89
  backoff: 'exponential',
66
90
  delay: 100,
67
- maxDelay: 10000
91
+ maxDelay: 10000,
68
92
  });
69
93
 
70
94
  // Only retry specific errors
71
95
  await retry(() => mightFail(), {
72
96
  retries: 3,
73
- shouldRetry: (err) => err.code === 'ECONNREFUSED'
97
+ shouldRetry: (err) => err.code === 'ECONNREFUSED',
74
98
  });
75
99
 
76
100
  // With jitter to prevent thundering herd
@@ -78,21 +102,21 @@ await retry(() => mightFail(), {
78
102
  retries: 3,
79
103
  backoff: 'exponential',
80
104
  delay: 100,
81
- jitter: 'full' // or 'equal' or 'decorrelated'
105
+ jitter: 'full', // or 'equal' or 'decorrelated'
82
106
  });
83
107
  ```
84
108
 
85
109
  **Options:**
86
110
 
87
- | Option | Type | Default | Description |
88
- |--------|------|---------|-------------|
89
- | `retries` | `number` | `3` | Maximum retry attempts |
90
- | `delay` | `number` | `1000` | Base delay in milliseconds |
91
- | `maxDelay` | `number` | `30000` | Maximum delay cap |
92
- | `backoff` | `'constant' \| 'linear' \| 'exponential'` | `'constant'` | Backoff strategy |
93
- | `jitter` | `'none' \| 'full' \| 'equal' \| 'decorrelated'` | `'none'` | Jitter strategy |
94
- | `shouldRetry` | `(error) => boolean` | `() => true` | Predicate to filter retryable errors |
95
- | `onRetry` | `(error, attempt, delay) => void` | - | Callback on each retry |
111
+ | Option | Type | Default | Description |
112
+ | ------------- | ----------------------------------------------- | ------------ | ------------------------------------ |
113
+ | `retries` | `number` | `3` | Maximum retry attempts |
114
+ | `delay` | `number` | `1000` | Base delay in milliseconds |
115
+ | `maxDelay` | `number` | `30000` | Maximum delay cap |
116
+ | `backoff` | `'constant' \| 'linear' \| 'exponential'` | `'constant'` | Backoff strategy |
117
+ | `jitter` | `'none' \| 'full' \| 'equal' \| 'decorrelated'` | `'none'` | Jitter strategy |
118
+ | `shouldRetry` | `(error) => boolean` | `() => true` | Predicate to filter retryable errors |
119
+ | `onRetry` | `(error, attempt, delay) => void` | - | Callback on each retry |
96
120
 
97
121
  ### `circuitBreaker(options)`
98
122
 
@@ -102,11 +126,11 @@ Fails fast when a service is degraded, preventing cascade failures.
102
126
  import { circuitBreaker } from '@git-stunts/alfred';
103
127
 
104
128
  const breaker = circuitBreaker({
105
- threshold: 5, // Open after 5 failures
106
- duration: 60000, // Stay open for 60 seconds
129
+ threshold: 5, // Open after 5 failures
130
+ duration: 60000, // Stay open for 60 seconds
107
131
  onOpen: () => console.log('Circuit opened!'),
108
132
  onClose: () => console.log('Circuit closed!'),
109
- onHalfOpen: () => console.log('Testing recovery...')
133
+ onHalfOpen: () => console.log('Testing recovery...'),
110
134
  });
111
135
 
112
136
  // Circuit has three states:
@@ -125,15 +149,15 @@ try {
125
149
 
126
150
  **Options:**
127
151
 
128
- | Option | Type | Default | Description |
129
- |--------|------|---------|-------------|
130
- | `threshold` | `number` | required | Failures before opening |
131
- | `duration` | `number` | required | How long to stay open (ms) |
132
- | `successThreshold` | `number` | `1` | Successes to close from half-open |
133
- | `shouldTrip` | `(error) => boolean` | `() => true` | Which errors count as failures |
134
- | `onOpen` | `() => void` | - | Called when circuit opens |
135
- | `onClose` | `() => void` | - | Called when circuit closes |
136
- | `onHalfOpen` | `() => void` | - | Called when entering half-open |
152
+ | Option | Type | Default | Description |
153
+ | ------------------ | -------------------- | ------------ | --------------------------------- |
154
+ | `threshold` | `number` | required | Failures before opening |
155
+ | `duration` | `number` | required | How long to stay open (ms) |
156
+ | `successThreshold` | `number` | `1` | Successes to close from half-open |
157
+ | `shouldTrip` | `(error) => boolean` | `() => true` | Which errors count as failures |
158
+ | `onOpen` | `() => void` | - | Called when circuit opens |
159
+ | `onClose` | `() => void` | - | Called when circuit closes |
160
+ | `onHalfOpen` | `() => void` | - | Called when entering half-open |
137
161
 
138
162
  ### `bulkhead(options)`
139
163
 
@@ -143,8 +167,8 @@ Limits the number of concurrent executions to prevent resource exhaustion.
143
167
  import { bulkhead } from '@git-stunts/alfred';
144
168
 
145
169
  const limiter = bulkhead({
146
- limit: 10, // Max 10 concurrent executions
147
- queueLimit: 20 // Max 20 pending requests in queue
170
+ limit: 10, // Max 10 concurrent executions
171
+ queueLimit: 20, // Max 20 pending requests in queue
148
172
  });
149
173
 
150
174
  // Returns an object with:
@@ -163,10 +187,10 @@ console.log(`Current load: ${limiter.stats.active} active tasks`);
163
187
 
164
188
  **Options:**
165
189
 
166
- | Option | Type | Default | Description |
167
- |--------|------|---------|-------------|
168
- | `limit` | `number` | required | Maximum concurrent executions |
169
- | `queueLimit` | `number` | `0` | Maximum pending requests in queue |
190
+ | Option | Type | Default | Description |
191
+ | ------------ | -------- | -------- | --------------------------------- |
192
+ | `limit` | `number` | required | Maximum concurrent executions |
193
+ | `queueLimit` | `number` | `0` | Maximum pending requests in queue |
170
194
 
171
195
  ### `timeout(ms, options)`
172
196
 
@@ -180,7 +204,7 @@ const result = await timeout(5000, () => slowOperation());
180
204
 
181
205
  // With callback
182
206
  const result = await timeout(5000, () => slowOperation(), {
183
- onTimeout: (elapsed) => console.log(`Timed out after ${elapsed}ms`)
207
+ onTimeout: (elapsed) => console.log(`Timed out after ${elapsed}ms`),
184
208
  });
185
209
  ```
186
210
 
@@ -211,10 +235,10 @@ Combines multiple policies. Policies execute from left to right (outermost to in
211
235
  import { compose, retry, circuitBreaker, timeout } from '@git-stunts/alfred';
212
236
 
213
237
  const resilient = compose(
214
- timeout(30000), // Total timeout
215
- retry({ retries: 3, backoff: 'exponential' }), // Retry failures
238
+ timeout(30000), // Total timeout
239
+ retry({ retries: 3, backoff: 'exponential' }), // Retry failures
216
240
  circuitBreaker({ threshold: 5, duration: 60000 }), // Fail fast if broken
217
- bulkhead({ limit: 5, queueLimit: 10 }) // Limit concurrency
241
+ bulkhead({ limit: 5, queueLimit: 10 }) // Limit concurrency
218
242
  );
219
243
 
220
244
  // Execution order:
@@ -229,23 +253,15 @@ await resilient.execute(() => riskyOperation());
229
253
  Alfred provides a composable telemetry system to monitor policy behavior.
230
254
 
231
255
  ```javascript
232
- import {
233
- Policy,
234
- ConsoleSink,
235
- InMemorySink,
236
- MultiSink
237
- } from '@git-stunts/alfred';
256
+ import { Policy, ConsoleSink, InMemorySink, MultiSink } from '@git-stunts/alfred';
238
257
 
239
258
  // 1. Create a sink (or multiple)
240
- const sink = new MultiSink([
241
- new ConsoleSink(),
242
- new InMemorySink()
243
- ]);
259
+ const sink = new MultiSink([new ConsoleSink(), new InMemorySink()]);
244
260
 
245
261
  // 2. Attach to policies
246
262
  const policy = Policy.retry({
247
263
  retries: 3,
248
- telemetry: sink
264
+ telemetry: sink,
249
265
  });
250
266
 
251
267
  // All policies emit events:
@@ -277,7 +293,7 @@ test('retries with exponential backoff', async () => {
277
293
  retries: 3,
278
294
  backoff: 'exponential',
279
295
  delay: 1000,
280
- clock
296
+ clock,
281
297
  });
282
298
 
283
299
  // First attempt fails immediately
@@ -299,11 +315,7 @@ test('retries with exponential backoff', async () => {
299
315
  ## Error Types
300
316
 
301
317
  ```javascript
302
- import {
303
- RetryExhaustedError,
304
- CircuitOpenError,
305
- TimeoutError
306
- } from '@git-stunts/alfred';
318
+ import { RetryExhaustedError, CircuitOpenError, TimeoutError } from '@git-stunts/alfred';
307
319
 
308
320
  try {
309
321
  await resilientOperation();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@git-stunts/alfred",
3
- "version": "0.2.0",
4
- "description": "Why do we fall, Bruce? Resilience patterns for async operations.",
3
+ "version": "0.3.0",
4
+ "description": "Why do we fall, Bruce? Production-grade resilience patterns for async operations.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
7
7
  "types": "./src/index.d.ts",
package/src/compose.js CHANGED
@@ -33,7 +33,7 @@
33
33
  export function compose(...policies) {
34
34
  if (policies.length === 0) {
35
35
  return {
36
- execute: (fn) => fn()
36
+ execute: (fn) => fn(),
37
37
  };
38
38
  }
39
39
 
@@ -55,7 +55,7 @@ export function compose(...policies) {
55
55
  }
56
56
 
57
57
  return chain();
58
- }
58
+ },
59
59
  };
60
60
  }
61
61
 
@@ -86,7 +86,7 @@ export function fallback(primary, secondary) {
86
86
  } catch {
87
87
  return await secondary.execute(fn);
88
88
  }
89
- }
89
+ },
90
90
  };
91
91
  }
92
92
 
@@ -148,6 +148,6 @@ export function race(policyA, policyB) {
148
148
  policyA.execute(fn).then(handleSuccess, (e) => handleFailure(e, true));
149
149
  policyB.execute(fn).then(handleSuccess, (e) => handleFailure(e, false));
150
150
  });
151
- }
151
+ },
152
152
  };
153
153
  }
package/src/index.d.ts CHANGED
@@ -1,125 +1,360 @@
1
+ /**
2
+ * @module @git-stunts/alfred
3
+ * @description Production-grade resilience patterns for async operations.
4
+ * Includes Retry, Circuit Breaker, Timeout, and Bulkhead policies.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * import { compose, retry, circuitBreaker, timeout } from "@git-stunts/alfred";
9
+ *
10
+ * const policy = compose(
11
+ * retry({ retries: 3 }),
12
+ * circuitBreaker({ threshold: 5, duration: 60000 }),
13
+ * timeout(5000)
14
+ * );
15
+ *
16
+ * await policy.execute(() => fetch("https://api.example.com"));
17
+ * ```
18
+ */
19
+
20
+ /**
21
+ * A value that can be either static or resolved dynamically via a function.
22
+ */
23
+ export type Resolvable<T> = T | (() => T);
24
+
25
+ /**
26
+ * Options for the Retry policy.
27
+ */
1
28
  export interface RetryOptions {
2
- retries?: number;
3
- delay?: number;
4
- maxDelay?: number;
5
- backoff?: 'constant' | 'linear' | 'exponential';
6
- jitter?: 'none' | 'full' | 'equal' | 'decorrelated';
29
+ /** Maximum number of retry attempts. Default: 3 */
30
+ retries?: Resolvable<number>;
31
+ /** Base delay in milliseconds. Default: 1000 */
32
+ delay?: Resolvable<number>;
33
+ /** Maximum delay cap in milliseconds. Default: 30000 */
34
+ maxDelay?: Resolvable<number>;
35
+ /** Backoff strategy. Default: 'constant' */
36
+ backoff?: Resolvable<'constant' | 'linear' | 'exponential'>;
37
+ /** Jitter strategy to prevent thundering herd. Default: 'none' */
38
+ jitter?: Resolvable<'none' | 'full' | 'equal' | 'decorrelated'>;
39
+ /** Predicate to determine if an error is retryable. Default: always true */
7
40
  shouldRetry?: (error: Error) => boolean;
41
+ /** Callback invoked before each retry. */
8
42
  onRetry?: (error: Error, attempt: number, delay: number) => void;
43
+ /** Telemetry sink for observability. */
9
44
  telemetry?: TelemetrySink;
45
+ /** Clock implementation for testing. */
10
46
  clock?: any;
11
47
  }
12
48
 
49
+ /**
50
+ * Options for the Circuit Breaker policy.
51
+ */
13
52
  export interface CircuitBreakerOptions {
14
- threshold: number;
15
- duration: number;
16
- successThreshold?: number;
53
+ /** Number of failures before opening the circuit. */
54
+ threshold: Resolvable<number>;
55
+ /** Milliseconds to stay open before transitioning to half-open. */
56
+ duration: Resolvable<number>;
57
+ /** Consecutive successes required to close the circuit from half-open. Default: 1 */
58
+ successThreshold?: Resolvable<number>;
59
+ /** Predicate to determine if an error counts as a failure. Default: always true */
17
60
  shouldTrip?: (error: Error) => boolean;
61
+ /** Callback when circuit opens. */
18
62
  onOpen?: () => void;
63
+ /** Callback when circuit closes. */
19
64
  onClose?: () => void;
65
+ /** Callback when circuit transitions to half-open. */
20
66
  onHalfOpen?: () => void;
67
+ /** Telemetry sink for observability. */
21
68
  telemetry?: TelemetrySink;
69
+ /** Clock implementation for testing. */
22
70
  clock?: any;
23
71
  }
24
72
 
73
+ /**
74
+ * Options for the Timeout policy.
75
+ */
25
76
  export interface TimeoutOptions {
77
+ /** Callback invoked when timeout occurs. */
26
78
  onTimeout?: (elapsed: number) => void;
79
+ /** Telemetry sink for observability. */
27
80
  telemetry?: TelemetrySink;
28
81
  }
29
82
 
83
+ /**
84
+ * Options for the Bulkhead policy.
85
+ */
30
86
  export interface BulkheadOptions {
31
- limit: number;
32
- queueLimit?: number;
87
+ /** Maximum concurrent executions. */
88
+ limit: Resolvable<number>;
89
+ /** Maximum pending requests in queue. Default: 0 */
90
+ queueLimit?: Resolvable<number>;
91
+ /** Telemetry sink for observability. */
92
+ telemetry?: TelemetrySink;
93
+ /** Clock implementation for testing. */
94
+ clock?: any;
95
+ }
96
+
97
+ /**
98
+ * Options for the Hedge policy.
99
+ */
100
+ export interface HedgeOptions {
101
+ /** Milliseconds to wait before spawning a hedge. */
102
+ delay: Resolvable<number>;
103
+ /** Maximum number of hedged attempts to spawn. Default: 1 */
104
+ maxHedges?: Resolvable<number>;
105
+ /** Telemetry sink for observability. */
33
106
  telemetry?: TelemetrySink;
107
+ /** Clock implementation for testing. */
34
108
  clock?: any;
35
109
  }
36
110
 
111
+ /**
112
+ * A structured event emitted by the telemetry system.
113
+ */
37
114
  export interface TelemetryEvent {
115
+ /** The type of event (e.g., 'retry.failure', 'circuit.open'). */
38
116
  type: string;
117
+ /** Unix timestamp of the event. */
39
118
  timestamp: number;
119
+ /** Metric increments (counters) to be aggregated by MetricsSink. */
120
+ metrics?: Record<string, number>;
121
+ /** Additional metadata (error, duration, attempts, etc.). */
40
122
  [key: string]: any;
41
123
  }
42
124
 
125
+ /**
126
+ * Interface for receiving telemetry events.
127
+ */
43
128
  export interface TelemetrySink {
129
+ /**
130
+ * Records a telemetry event.
131
+ * @param event The structured event.
132
+ */
44
133
  emit(event: TelemetryEvent): void;
45
134
  }
46
135
 
136
+ /**
137
+ * Stores telemetry events in an in-memory array. Useful for testing.
138
+ */
47
139
  export class InMemorySink implements TelemetrySink {
48
140
  events: TelemetryEvent[];
49
141
  emit(event: TelemetryEvent): void;
50
142
  clear(): void;
51
143
  }
52
144
 
145
+ /**
146
+ * Logs telemetry events to the console (stdout).
147
+ */
53
148
  export class ConsoleSink implements TelemetrySink {
54
149
  emit(event: TelemetryEvent): void;
55
150
  }
56
151
 
152
+ /**
153
+ * Discards all telemetry events.
154
+ */
57
155
  export class NoopSink implements TelemetrySink {
58
156
  emit(event: TelemetryEvent): void;
59
157
  }
60
158
 
159
+ /**
160
+ * Broadcasts telemetry events to multiple other sinks.
161
+ */
61
162
  export class MultiSink implements TelemetrySink {
62
163
  constructor(sinks: TelemetrySink[]);
63
164
  emit(event: TelemetryEvent): void;
64
165
  }
65
166
 
167
+ /**
168
+ * Sink that aggregates metrics in memory.
169
+ */
170
+ export class MetricsSink implements TelemetrySink {
171
+ emit(event: TelemetryEvent): void;
172
+ /** Returns a snapshot of the current metrics. */
173
+ get stats(): {
174
+ retries: number;
175
+ failures: number;
176
+ successes: number;
177
+ circuitBreaks: number;
178
+ circuitRejections: number;
179
+ bulkheadRejections: number;
180
+ timeouts: number;
181
+ hedges: number;
182
+ latency: {
183
+ count: number;
184
+ sum: number;
185
+ min: number;
186
+ max: number;
187
+ avg: number;
188
+ };
189
+ [key: string]: number | { count: number; sum: number; min: number; max: number; avg: number };
190
+ };
191
+ /** Resets all metrics to zero. */
192
+ clear(): void;
193
+ }
194
+
195
+ /**
196
+ * Error thrown when all retry attempts are exhausted.
197
+ */
66
198
  export class RetryExhaustedError extends Error {
67
199
  attempts: number;
68
200
  cause: Error;
69
201
  constructor(attempts: number, cause: Error);
70
202
  }
71
203
 
204
+ /**
205
+ * Error thrown when the circuit breaker is open (OPEN state).
206
+ */
72
207
  export class CircuitOpenError extends Error {
73
208
  openedAt: Date;
74
209
  failureCount: number;
75
210
  constructor(openedAt: Date, failureCount: number);
76
211
  }
77
212
 
213
+ /**
214
+ * Error thrown when an operation exceeds its time limit.
215
+ */
78
216
  export class TimeoutError extends Error {
79
217
  timeout: number;
80
218
  elapsed: number;
81
219
  constructor(timeout: number, elapsed: number);
82
220
  }
83
221
 
222
+ /**
223
+ * Error thrown when the bulkhead limit and queue are both full.
224
+ */
84
225
  export class BulkheadRejectedError extends Error {
85
226
  limit: number;
86
227
  queueLimit: number;
87
228
  constructor(limit: number, queueLimit: number);
88
229
  }
89
230
 
231
+ /**
232
+ * Executes an async function with configurable retry logic.
233
+ *
234
+ * @param fn The async operation to execute.
235
+ * @param options Retry configuration options.
236
+ * @returns The result of the operation.
237
+ * @throws {RetryExhaustedError} If all retries fail.
238
+ */
90
239
  export function retry<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
91
240
 
241
+ /**
242
+ * Represents a Circuit Breaker instance.
243
+ */
92
244
  export interface CircuitBreaker {
245
+ /**
246
+ * Executes a function with circuit breaker protection.
247
+ */
93
248
  execute<T>(fn: () => Promise<T>): Promise<T>;
249
+ /**
250
+ * Current state of the circuit.
251
+ */
94
252
  readonly state: 'CLOSED' | 'OPEN' | 'HALF_OPEN';
95
253
  }
96
254
 
255
+ /**
256
+ * Creates a Circuit Breaker policy.
257
+ *
258
+ * @param options Configuration options.
259
+ */
97
260
  export function circuitBreaker(options: CircuitBreakerOptions): CircuitBreaker;
98
261
 
99
- export function timeout<T>(ms: number, fn: ((signal: AbortSignal) => Promise<T>) | (() => Promise<T>), options?: TimeoutOptions): Promise<T>;
262
+ /**
263
+ * Executes a function with a time limit.
264
+ *
265
+ * @param ms Timeout duration in milliseconds.
266
+ * @param fn The function to execute. Accepts an AbortSignal if defined.
267
+ * @param options Configuration options.
268
+ */
269
+ export function timeout<T>(
270
+ ms: Resolvable<number>,
271
+ fn: ((signal: AbortSignal) => Promise<T>) | (() => Promise<T>),
272
+ options?: TimeoutOptions
273
+ ): Promise<T>;
100
274
 
275
+ /**
276
+ * Represents a Bulkhead instance.
277
+ */
101
278
  export interface Bulkhead {
279
+ /**
280
+ * Executes a function with concurrency limiting.
281
+ */
102
282
  execute<T>(fn: () => Promise<T>): Promise<T>;
283
+ /**
284
+ * Current load statistics.
285
+ */
103
286
  readonly stats: { active: number; pending: number; available: number };
104
287
  }
105
288
 
289
+ /**
290
+ * Creates a Bulkhead policy for concurrency limiting.
291
+ *
292
+ * @param options Configuration options.
293
+ */
106
294
  export function bulkhead(options: BulkheadOptions): Bulkhead;
107
295
 
296
+ /**
297
+ * Represents a Hedge policy instance.
298
+ */
299
+ export interface Hedge {
300
+ execute<T>(fn: (signal?: AbortSignal) => Promise<T>): Promise<T>;
301
+ }
302
+
303
+ /**
304
+ * Creates a Hedge policy for speculative execution.
305
+ * @param options Configuration options.
306
+ */
307
+ export function hedge(options: HedgeOptions): Hedge;
308
+
309
+ /**
310
+ * Composes multiple policies into a single executable policy.
311
+ * Policies execute from left to right (outermost to innermost).
312
+ *
313
+ * @param policies The policies to compose.
314
+ */
108
315
  export function compose(...policies: any[]): { execute<T>(fn: () => Promise<T>): Promise<T> };
109
- export function fallback(primary: any, secondary: any): { execute<T>(fn: () => Promise<T>): Promise<T> };
110
- export function race(primary: any, secondary: any): { execute<T>(fn: () => Promise<T>): Promise<T> };
111
316
 
317
+ /**
318
+ * Creates a fallback policy. If the primary policy fails, the secondary is executed.
319
+ */
320
+ export function fallback(
321
+ primary: any,
322
+ secondary: any
323
+ ): { execute<T>(fn: () => Promise<T>): Promise<T> };
324
+
325
+ /**
326
+ * Creates a race policy. Executes both policies concurrently; the first to succeed wins.
327
+ */
328
+ export function race(
329
+ primary: any,
330
+ secondary: any
331
+ ): { execute<T>(fn: () => Promise<T>): Promise<T> };
332
+
333
+ /**
334
+ * Fluent API for building resilience policies.
335
+ */
112
336
  export class Policy {
113
337
  constructor(executor: (fn: () => Promise<any>) => Promise<any>);
338
+ /** Creates a Retry policy wrapper. */
114
339
  static retry(options?: RetryOptions): Policy;
340
+ /** Creates a Circuit Breaker policy wrapper. */
115
341
  static circuitBreaker(options: CircuitBreakerOptions): Policy;
116
- static timeout(ms: number, options?: TimeoutOptions): Policy;
342
+ /** Creates a Timeout policy wrapper. */
343
+ static timeout(ms: Resolvable<number>, options?: TimeoutOptions): Policy;
344
+ /** Creates a Bulkhead policy wrapper. */
117
345
  static bulkhead(options: BulkheadOptions): Policy;
346
+ /** Creates a Hedge policy wrapper. */
347
+ static hedge(options: HedgeOptions): Policy;
348
+ /** Creates a pass-through (no-op) policy. */
118
349
  static noop(): Policy;
119
350
 
351
+ /** Wraps this policy with another (sequential composition). */
120
352
  wrap(otherPolicy: Policy): Policy;
353
+ /** Falls back to another policy if this one fails. */
121
354
  or(otherPolicy: Policy): Policy;
355
+ /** Races this policy against another. */
122
356
  race(otherPolicy: Policy): Policy;
357
+ /** Executes the policy chain. */
123
358
  execute<T>(fn: () => Promise<T>): Promise<T>;
124
359
  }
125
360