@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.
Files changed (3) hide show
  1. package/README.md +20 -0
  2. package/package.json +3 -2
  3. 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
+ [![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
+
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.0",
4
- "description": "Why do we fall, Bruce? Resilience patterns for async operations.",
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
+ }