@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 +70 -58
- package/package.json +2 -2
- package/src/compose.js +4 -4
- package/src/index.d.ts +249 -14
- package/src/index.js +8 -4
- package/src/policies/bulkhead.js +60 -32
- package/src/policies/circuit-breaker.js +22 -13
- package/src/policies/hedge.js +123 -0
- package/src/policies/retry.js +34 -24
- package/src/policies/timeout.js +8 -5
- package/src/policy.js +15 -12
- package/src/telemetry.js +84 -1
- package/src/testing.d.ts +14 -3
- package/src/utils/clock.js +3 -3
- package/src/utils/resolvable.js +10 -0
package/README.md
CHANGED
|
@@ -1,20 +1,43 @@
|
|
|
1
1
|
# @git-stunts/alfred
|
|
2
2
|
|
|
3
|
-
|
|
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
|
+
[](https://jsr.io/@git-stunts/alfred)
|
|
14
|
+
[](https://www.npmjs.com/package/@git-stunts/alfred)
|
|
15
|
+
[](https://github.com/git-stunts/alfred/actions/workflows/ci.yml)
|
|
16
|
+
|
|
17
|
+
> _"Why do we fall, Bruce?"_
|
|
4
18
|
>
|
|
5
|
-
>
|
|
19
|
+
> _"So we can `retry({ backoff: 'exponential', jitter: 'decorrelated' })`."_
|
|
6
20
|
|
|
7
|
-
Resilience patterns for async operations.
|
|
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
|
-
|
|
32
|
-
|
|
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
|
|
88
|
-
|
|
89
|
-
| `retries`
|
|
90
|
-
| `delay`
|
|
91
|
-
| `maxDelay`
|
|
92
|
-
| `backoff`
|
|
93
|
-
| `jitter`
|
|
94
|
-
| `shouldRetry` | `(error) => boolean`
|
|
95
|
-
| `onRetry`
|
|
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,
|
|
106
|
-
duration: 60000,
|
|
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
|
|
129
|
-
|
|
130
|
-
| `threshold`
|
|
131
|
-
| `duration`
|
|
132
|
-
| `successThreshold` | `number`
|
|
133
|
-
| `shouldTrip`
|
|
134
|
-
| `onOpen`
|
|
135
|
-
| `onClose`
|
|
136
|
-
| `onHalfOpen`
|
|
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,
|
|
147
|
-
queueLimit: 20
|
|
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
|
|
167
|
-
|
|
168
|
-
| `limit`
|
|
169
|
-
| `queueLimit` | `number` | `0`
|
|
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),
|
|
215
|
-
retry({ retries: 3, backoff: 'exponential' }),
|
|
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 })
|
|
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.
|
|
4
|
-
"description": "Why do we fall, Bruce?
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|