@git-stunts/alfred 0.2.0 → 0.2.1
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 +20 -0
- package/package.json +3 -2
- package/src/index.d.ts +162 -1
package/README.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @git-stunts/alfred
|
|
2
2
|
|
|
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
|
+
|
|
3
17
|
> *"Why do we fall, Bruce?"*
|
|
4
18
|
>
|
|
5
19
|
> *"So we can `retry({ backoff: 'exponential', jitter: 'decorrelated' })`."*
|
|
@@ -8,10 +22,16 @@ Resilience patterns for async operations. *Tuff 'nuff for most stuff!*
|
|
|
8
22
|
|
|
9
23
|
## Installation
|
|
10
24
|
|
|
25
|
+
### NPM
|
|
11
26
|
```bash
|
|
12
27
|
npm install @git-stunts/alfred
|
|
13
28
|
```
|
|
14
29
|
|
|
30
|
+
### JSR (Deno, Bun, Node)
|
|
31
|
+
```bash
|
|
32
|
+
npx jsr add @git-stunts/alfred
|
|
33
|
+
```
|
|
34
|
+
|
|
15
35
|
## Multi-Runtime Support
|
|
16
36
|
|
|
17
37
|
Alfred is designed to be platform-agnostic and is tested against:
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@git-stunts/alfred",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Why do we fall, Bruce?
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Why do we fall, Bruce? Production-grade resilience patterns for async operations.",
|
|
5
|
+
|
|
5
6
|
"type": "module",
|
|
6
7
|
"main": "src/index.js",
|
|
7
8
|
"types": "./src/index.d.ts",
|
package/src/index.d.ts
CHANGED
|
@@ -1,125 +1,286 @@
|
|
|
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
|
+
* Options for the Retry policy.
|
|
22
|
+
*/
|
|
1
23
|
export interface RetryOptions {
|
|
24
|
+
/** Maximum number of retry attempts. Default: 3 */
|
|
2
25
|
retries?: number;
|
|
26
|
+
/** Base delay in milliseconds. Default: 1000 */
|
|
3
27
|
delay?: number;
|
|
28
|
+
/** Maximum delay cap in milliseconds. Default: 30000 */
|
|
4
29
|
maxDelay?: number;
|
|
30
|
+
/** Backoff strategy. Default: 'constant' */
|
|
5
31
|
backoff?: 'constant' | 'linear' | 'exponential';
|
|
32
|
+
/** Jitter strategy to prevent thundering herd. Default: 'none' */
|
|
6
33
|
jitter?: 'none' | 'full' | 'equal' | 'decorrelated';
|
|
34
|
+
/** Predicate to determine if an error is retryable. Default: always true */
|
|
7
35
|
shouldRetry?: (error: Error) => boolean;
|
|
36
|
+
/** Callback invoked before each retry. */
|
|
8
37
|
onRetry?: (error: Error, attempt: number, delay: number) => void;
|
|
38
|
+
/** Telemetry sink for observability. */
|
|
9
39
|
telemetry?: TelemetrySink;
|
|
40
|
+
/** Clock implementation for testing. */
|
|
10
41
|
clock?: any;
|
|
11
42
|
}
|
|
12
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Options for the Circuit Breaker policy.
|
|
46
|
+
*/
|
|
13
47
|
export interface CircuitBreakerOptions {
|
|
48
|
+
/** Number of failures before opening the circuit. */
|
|
14
49
|
threshold: number;
|
|
50
|
+
/** Milliseconds to stay open before transitioning to half-open. */
|
|
15
51
|
duration: number;
|
|
52
|
+
/** Consecutive successes required to close the circuit from half-open. Default: 1 */
|
|
16
53
|
successThreshold?: number;
|
|
54
|
+
/** Predicate to determine if an error counts as a failure. Default: always true */
|
|
17
55
|
shouldTrip?: (error: Error) => boolean;
|
|
56
|
+
/** Callback when circuit opens. */
|
|
18
57
|
onOpen?: () => void;
|
|
58
|
+
/** Callback when circuit closes. */
|
|
19
59
|
onClose?: () => void;
|
|
60
|
+
/** Callback when circuit transitions to half-open. */
|
|
20
61
|
onHalfOpen?: () => void;
|
|
62
|
+
/** Telemetry sink for observability. */
|
|
21
63
|
telemetry?: TelemetrySink;
|
|
64
|
+
/** Clock implementation for testing. */
|
|
22
65
|
clock?: any;
|
|
23
66
|
}
|
|
24
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Options for the Timeout policy.
|
|
70
|
+
*/
|
|
25
71
|
export interface TimeoutOptions {
|
|
72
|
+
/** Callback invoked when timeout occurs. */
|
|
26
73
|
onTimeout?: (elapsed: number) => void;
|
|
74
|
+
/** Telemetry sink for observability. */
|
|
27
75
|
telemetry?: TelemetrySink;
|
|
28
76
|
}
|
|
29
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Options for the Bulkhead policy.
|
|
80
|
+
*/
|
|
30
81
|
export interface BulkheadOptions {
|
|
82
|
+
/** Maximum concurrent executions. */
|
|
31
83
|
limit: number;
|
|
84
|
+
/** Maximum pending requests in queue. Default: 0 */
|
|
32
85
|
queueLimit?: number;
|
|
86
|
+
/** Telemetry sink for observability. */
|
|
33
87
|
telemetry?: TelemetrySink;
|
|
88
|
+
/** Clock implementation for testing. */
|
|
34
89
|
clock?: any;
|
|
35
90
|
}
|
|
36
91
|
|
|
92
|
+
/**
|
|
93
|
+
* A structured event emitted by the telemetry system.
|
|
94
|
+
*/
|
|
37
95
|
export interface TelemetryEvent {
|
|
96
|
+
/** The type of event (e.g., 'retry.failure', 'circuit.open'). */
|
|
38
97
|
type: string;
|
|
98
|
+
/** Unix timestamp of the event. */
|
|
39
99
|
timestamp: number;
|
|
100
|
+
/** Additional metadata (error, duration, attempts, etc.). */
|
|
40
101
|
[key: string]: any;
|
|
41
102
|
}
|
|
42
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Interface for receiving telemetry events.
|
|
106
|
+
*/
|
|
43
107
|
export interface TelemetrySink {
|
|
108
|
+
/**
|
|
109
|
+
* Records a telemetry event.
|
|
110
|
+
* @param event The structured event.
|
|
111
|
+
*/
|
|
44
112
|
emit(event: TelemetryEvent): void;
|
|
45
113
|
}
|
|
46
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Stores telemetry events in an in-memory array. Useful for testing.
|
|
117
|
+
*/
|
|
47
118
|
export class InMemorySink implements TelemetrySink {
|
|
48
119
|
events: TelemetryEvent[];
|
|
49
120
|
emit(event: TelemetryEvent): void;
|
|
50
121
|
clear(): void;
|
|
51
122
|
}
|
|
52
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Logs telemetry events to the console (stdout).
|
|
126
|
+
*/
|
|
53
127
|
export class ConsoleSink implements TelemetrySink {
|
|
54
128
|
emit(event: TelemetryEvent): void;
|
|
55
129
|
}
|
|
56
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Discards all telemetry events.
|
|
133
|
+
*/
|
|
57
134
|
export class NoopSink implements TelemetrySink {
|
|
58
135
|
emit(event: TelemetryEvent): void;
|
|
59
136
|
}
|
|
60
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Broadcasts telemetry events to multiple other sinks.
|
|
140
|
+
*/
|
|
61
141
|
export class MultiSink implements TelemetrySink {
|
|
62
142
|
constructor(sinks: TelemetrySink[]);
|
|
63
143
|
emit(event: TelemetryEvent): void;
|
|
64
144
|
}
|
|
65
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Error thrown when all retry attempts are exhausted.
|
|
148
|
+
*/
|
|
66
149
|
export class RetryExhaustedError extends Error {
|
|
67
150
|
attempts: number;
|
|
68
151
|
cause: Error;
|
|
69
152
|
constructor(attempts: number, cause: Error);
|
|
70
153
|
}
|
|
71
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Error thrown when the circuit breaker is open (OPEN state).
|
|
157
|
+
*/
|
|
72
158
|
export class CircuitOpenError extends Error {
|
|
73
159
|
openedAt: Date;
|
|
74
160
|
failureCount: number;
|
|
75
161
|
constructor(openedAt: Date, failureCount: number);
|
|
76
162
|
}
|
|
77
163
|
|
|
164
|
+
/**
|
|
165
|
+
* Error thrown when an operation exceeds its time limit.
|
|
166
|
+
*/
|
|
78
167
|
export class TimeoutError extends Error {
|
|
79
168
|
timeout: number;
|
|
80
169
|
elapsed: number;
|
|
81
170
|
constructor(timeout: number, elapsed: number);
|
|
82
171
|
}
|
|
83
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Error thrown when the bulkhead limit and queue are both full.
|
|
175
|
+
*/
|
|
84
176
|
export class BulkheadRejectedError extends Error {
|
|
85
177
|
limit: number;
|
|
86
178
|
queueLimit: number;
|
|
87
179
|
constructor(limit: number, queueLimit: number);
|
|
88
180
|
}
|
|
89
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Executes an async function with configurable retry logic.
|
|
184
|
+
*
|
|
185
|
+
* @param fn The async operation to execute.
|
|
186
|
+
* @param options Retry configuration options.
|
|
187
|
+
* @returns The result of the operation.
|
|
188
|
+
* @throws {RetryExhaustedError} If all retries fail.
|
|
189
|
+
*/
|
|
90
190
|
export function retry<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
|
|
91
191
|
|
|
192
|
+
/**
|
|
193
|
+
* Represents a Circuit Breaker instance.
|
|
194
|
+
*/
|
|
92
195
|
export interface CircuitBreaker {
|
|
196
|
+
/**
|
|
197
|
+
* Executes a function with circuit breaker protection.
|
|
198
|
+
*/
|
|
93
199
|
execute<T>(fn: () => Promise<T>): Promise<T>;
|
|
200
|
+
/**
|
|
201
|
+
* Current state of the circuit.
|
|
202
|
+
*/
|
|
94
203
|
readonly state: 'CLOSED' | 'OPEN' | 'HALF_OPEN';
|
|
95
204
|
}
|
|
96
205
|
|
|
206
|
+
/**
|
|
207
|
+
* Creates a Circuit Breaker policy.
|
|
208
|
+
*
|
|
209
|
+
* @param options Configuration options.
|
|
210
|
+
*/
|
|
97
211
|
export function circuitBreaker(options: CircuitBreakerOptions): CircuitBreaker;
|
|
98
212
|
|
|
213
|
+
/**
|
|
214
|
+
* Executes a function with a time limit.
|
|
215
|
+
*
|
|
216
|
+
* @param ms Timeout duration in milliseconds.
|
|
217
|
+
* @param fn The function to execute. Accepts an AbortSignal if defined.
|
|
218
|
+
* @param options Configuration options.
|
|
219
|
+
*/
|
|
99
220
|
export function timeout<T>(ms: number, fn: ((signal: AbortSignal) => Promise<T>) | (() => Promise<T>), options?: TimeoutOptions): Promise<T>;
|
|
100
221
|
|
|
222
|
+
/**
|
|
223
|
+
* Represents a Bulkhead instance.
|
|
224
|
+
*/
|
|
101
225
|
export interface Bulkhead {
|
|
226
|
+
/**
|
|
227
|
+
* Executes a function with concurrency limiting.
|
|
228
|
+
*/
|
|
102
229
|
execute<T>(fn: () => Promise<T>): Promise<T>;
|
|
230
|
+
/**
|
|
231
|
+
* Current load statistics.
|
|
232
|
+
*/
|
|
103
233
|
readonly stats: { active: number; pending: number; available: number };
|
|
104
234
|
}
|
|
105
235
|
|
|
236
|
+
/**
|
|
237
|
+
* Creates a Bulkhead policy for concurrency limiting.
|
|
238
|
+
*
|
|
239
|
+
* @param options Configuration options.
|
|
240
|
+
*/
|
|
106
241
|
export function bulkhead(options: BulkheadOptions): Bulkhead;
|
|
107
242
|
|
|
243
|
+
/**
|
|
244
|
+
* Composes multiple policies into a single executable policy.
|
|
245
|
+
* Policies execute from left to right (outermost to innermost).
|
|
246
|
+
*
|
|
247
|
+
* @param policies The policies to compose.
|
|
248
|
+
*/
|
|
108
249
|
export function compose(...policies: any[]): { execute<T>(fn: () => Promise<T>): Promise<T> };
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Creates a fallback policy. If the primary policy fails, the secondary is executed.
|
|
253
|
+
*/
|
|
109
254
|
export function fallback(primary: any, secondary: any): { execute<T>(fn: () => Promise<T>): Promise<T> };
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Creates a race policy. Executes both policies concurrently; the first to succeed wins.
|
|
258
|
+
*/
|
|
110
259
|
export function race(primary: any, secondary: any): { execute<T>(fn: () => Promise<T>): Promise<T> };
|
|
111
260
|
|
|
261
|
+
/**
|
|
262
|
+
* Fluent API for building resilience policies.
|
|
263
|
+
*/
|
|
112
264
|
export class Policy {
|
|
113
265
|
constructor(executor: (fn: () => Promise<any>) => Promise<any>);
|
|
266
|
+
/** Creates a Retry policy wrapper. */
|
|
114
267
|
static retry(options?: RetryOptions): Policy;
|
|
268
|
+
/** Creates a Circuit Breaker policy wrapper. */
|
|
115
269
|
static circuitBreaker(options: CircuitBreakerOptions): Policy;
|
|
270
|
+
/** Creates a Timeout policy wrapper. */
|
|
116
271
|
static timeout(ms: number, options?: TimeoutOptions): Policy;
|
|
272
|
+
/** Creates a Bulkhead policy wrapper. */
|
|
117
273
|
static bulkhead(options: BulkheadOptions): Policy;
|
|
274
|
+
/** Creates a pass-through (no-op) policy. */
|
|
118
275
|
static noop(): Policy;
|
|
119
276
|
|
|
277
|
+
/** Wraps this policy with another (sequential composition). */
|
|
120
278
|
wrap(otherPolicy: Policy): Policy;
|
|
279
|
+
/** Falls back to another policy if this one fails. */
|
|
121
280
|
or(otherPolicy: Policy): Policy;
|
|
281
|
+
/** Races this policy against another. */
|
|
122
282
|
race(otherPolicy: Policy): Policy;
|
|
283
|
+
/** Executes the policy chain. */
|
|
123
284
|
execute<T>(fn: () => Promise<T>): Promise<T>;
|
|
124
285
|
}
|
|
125
286
|
|
|
@@ -133,4 +294,4 @@ export class TestClock {
|
|
|
133
294
|
sleep(ms: number): Promise<void>;
|
|
134
295
|
tick(ms?: number): Promise<void>;
|
|
135
296
|
advance(ms: number): Promise<void>;
|
|
136
|
-
}
|
|
297
|
+
}
|