awaitly 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.
- package/LICENSE +21 -0
- package/README.md +1278 -0
- package/dist/batch.cjs +2 -0
- package/dist/batch.cjs.map +1 -0
- package/dist/batch.d.cts +197 -0
- package/dist/batch.d.ts +197 -0
- package/dist/batch.js +2 -0
- package/dist/batch.js.map +1 -0
- package/dist/circuit-breaker.cjs +2 -0
- package/dist/circuit-breaker.cjs.map +1 -0
- package/dist/circuit-breaker.d.cts +208 -0
- package/dist/circuit-breaker.d.ts +208 -0
- package/dist/circuit-breaker.js +2 -0
- package/dist/circuit-breaker.js.map +1 -0
- package/dist/conditional.cjs +2 -0
- package/dist/conditional.cjs.map +1 -0
- package/dist/conditional.d.cts +249 -0
- package/dist/conditional.d.ts +249 -0
- package/dist/conditional.js +2 -0
- package/dist/conditional.js.map +1 -0
- package/dist/core-BuTBsR0x.d.cts +2325 -0
- package/dist/core-BuTBsR0x.d.ts +2325 -0
- package/dist/core.cjs +2 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +3 -0
- package/dist/core.d.ts +3 -0
- package/dist/core.js +2 -0
- package/dist/core.js.map +1 -0
- package/dist/devtools.cjs +11 -0
- package/dist/devtools.cjs.map +1 -0
- package/dist/devtools.d.cts +176 -0
- package/dist/devtools.d.ts +176 -0
- package/dist/devtools.js +11 -0
- package/dist/devtools.js.map +1 -0
- package/dist/duration.cjs +2 -0
- package/dist/duration.cjs.map +1 -0
- package/dist/duration.d.cts +246 -0
- package/dist/duration.d.ts +246 -0
- package/dist/duration.js +2 -0
- package/dist/duration.js.map +1 -0
- package/dist/hitl.cjs +2 -0
- package/dist/hitl.cjs.map +1 -0
- package/dist/hitl.d.cts +337 -0
- package/dist/hitl.d.ts +337 -0
- package/dist/hitl.js +2 -0
- package/dist/hitl.js.map +1 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/match.cjs +2 -0
- package/dist/match.cjs.map +1 -0
- package/dist/match.d.cts +209 -0
- package/dist/match.d.ts +209 -0
- package/dist/match.js +2 -0
- package/dist/match.js.map +1 -0
- package/dist/otel.cjs +2 -0
- package/dist/otel.cjs.map +1 -0
- package/dist/otel.d.cts +185 -0
- package/dist/otel.d.ts +185 -0
- package/dist/otel.js +2 -0
- package/dist/otel.js.map +1 -0
- package/dist/persistence.cjs +2 -0
- package/dist/persistence.cjs.map +1 -0
- package/dist/persistence.d.cts +572 -0
- package/dist/persistence.d.ts +572 -0
- package/dist/persistence.js +2 -0
- package/dist/persistence.js.map +1 -0
- package/dist/policies.cjs +2 -0
- package/dist/policies.cjs.map +1 -0
- package/dist/policies.d.cts +378 -0
- package/dist/policies.d.ts +378 -0
- package/dist/policies.js +2 -0
- package/dist/policies.js.map +1 -0
- package/dist/ratelimit.cjs +2 -0
- package/dist/ratelimit.cjs.map +1 -0
- package/dist/ratelimit.d.cts +279 -0
- package/dist/ratelimit.d.ts +279 -0
- package/dist/ratelimit.js +2 -0
- package/dist/ratelimit.js.map +1 -0
- package/dist/reliability.cjs +2 -0
- package/dist/reliability.cjs.map +1 -0
- package/dist/reliability.d.cts +5 -0
- package/dist/reliability.d.ts +5 -0
- package/dist/reliability.js +2 -0
- package/dist/reliability.js.map +1 -0
- package/dist/resource.cjs +2 -0
- package/dist/resource.cjs.map +1 -0
- package/dist/resource.d.cts +171 -0
- package/dist/resource.d.ts +171 -0
- package/dist/resource.js +2 -0
- package/dist/resource.js.map +1 -0
- package/dist/retry.cjs +2 -0
- package/dist/retry.cjs.map +1 -0
- package/dist/retry.d.cts +2 -0
- package/dist/retry.d.ts +2 -0
- package/dist/retry.js +2 -0
- package/dist/retry.js.map +1 -0
- package/dist/saga.cjs +2 -0
- package/dist/saga.cjs.map +1 -0
- package/dist/saga.d.cts +231 -0
- package/dist/saga.d.ts +231 -0
- package/dist/saga.js +2 -0
- package/dist/saga.js.map +1 -0
- package/dist/schedule.cjs +2 -0
- package/dist/schedule.cjs.map +1 -0
- package/dist/schedule.d.cts +387 -0
- package/dist/schedule.d.ts +387 -0
- package/dist/schedule.js +2 -0
- package/dist/schedule.js.map +1 -0
- package/dist/tagged-error.cjs +2 -0
- package/dist/tagged-error.cjs.map +1 -0
- package/dist/tagged-error.d.cts +252 -0
- package/dist/tagged-error.d.ts +252 -0
- package/dist/tagged-error.js +2 -0
- package/dist/tagged-error.js.map +1 -0
- package/dist/testing.cjs +2 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +228 -0
- package/dist/testing.d.ts +228 -0
- package/dist/testing.js +2 -0
- package/dist/testing.js.map +1 -0
- package/dist/visualize.cjs +1573 -0
- package/dist/visualize.cjs.map +1 -0
- package/dist/visualize.d.cts +1415 -0
- package/dist/visualize.d.ts +1415 -0
- package/dist/visualize.js +1573 -0
- package/dist/visualize.js.map +1 -0
- package/dist/webhook.cjs +2 -0
- package/dist/webhook.cjs.map +1 -0
- package/dist/webhook.d.cts +469 -0
- package/dist/webhook.d.ts +469 -0
- package/dist/webhook.js +2 -0
- package/dist/webhook.js.map +1 -0
- package/dist/workflow-entry-C6nH8ByN.d.ts +858 -0
- package/dist/workflow-entry-RRTlSg_4.d.cts +858 -0
- package/dist/workflow.cjs +2 -0
- package/dist/workflow.cjs.map +1 -0
- package/dist/workflow.d.cts +2 -0
- package/dist/workflow.d.ts +2 -0
- package/dist/workflow.js +2 -0
- package/dist/workflow.js.map +1 -0
- package/docs/advanced.md +1548 -0
- package/docs/api.md +513 -0
- package/docs/coming-from-neverthrow.md +1013 -0
- package/docs/match.md +417 -0
- package/docs/pino-logging-example.md +396 -0
- package/docs/policies.md +508 -0
- package/docs/resource-management.md +509 -0
- package/docs/schedule.md +467 -0
- package/docs/tagged-error.md +785 -0
- package/docs/visualization.md +430 -0
- package/docs/visualize-examples.md +330 -0
- package/package.json +227 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { R as Result, A as AsyncResult } from './core-BuTBsR0x.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Circuit Breaker for Steps
|
|
5
|
+
*
|
|
6
|
+
* Prevents cascading failures by tracking step failure rates and
|
|
7
|
+
* short-circuiting calls when a threshold is exceeded.
|
|
8
|
+
*
|
|
9
|
+
* Uses the circuit breaker pattern with three states:
|
|
10
|
+
* - CLOSED: Normal operation (steps executing)
|
|
11
|
+
* - OPEN: Fast-fail mode (steps blocked)
|
|
12
|
+
* - HALF_OPEN: Testing if service recovered
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import { createCircuitBreaker } from 'awaitly';
|
|
17
|
+
*
|
|
18
|
+
* const breaker = createCircuitBreaker({
|
|
19
|
+
* failureThreshold: 5,
|
|
20
|
+
* resetTimeout: 30000,
|
|
21
|
+
* halfOpenMax: 3,
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* const result = await workflow(async (step) => {
|
|
25
|
+
* const data = await breaker.execute(
|
|
26
|
+
* () => step(() => callExternalApi()),
|
|
27
|
+
* { name: 'external-api' }
|
|
28
|
+
* );
|
|
29
|
+
* return data;
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Circuit breaker state.
|
|
36
|
+
*/
|
|
37
|
+
type CircuitState = "CLOSED" | "OPEN" | "HALF_OPEN";
|
|
38
|
+
/**
|
|
39
|
+
* Configuration for circuit breaker behavior.
|
|
40
|
+
*/
|
|
41
|
+
interface CircuitBreakerConfig {
|
|
42
|
+
/**
|
|
43
|
+
* Number of failures within the window before opening the circuit.
|
|
44
|
+
* @default 5
|
|
45
|
+
*/
|
|
46
|
+
failureThreshold: number;
|
|
47
|
+
/**
|
|
48
|
+
* Time in ms to wait before transitioning from OPEN to HALF_OPEN.
|
|
49
|
+
* @default 30000 (30 seconds)
|
|
50
|
+
*/
|
|
51
|
+
resetTimeout: number;
|
|
52
|
+
/**
|
|
53
|
+
* Time window in ms for counting failures.
|
|
54
|
+
* Failures older than this are discarded.
|
|
55
|
+
* @default 60000 (1 minute)
|
|
56
|
+
*/
|
|
57
|
+
windowSize: number;
|
|
58
|
+
/**
|
|
59
|
+
* Maximum number of test requests allowed in HALF_OPEN state.
|
|
60
|
+
* If all succeed, circuit closes. If any fail, circuit reopens.
|
|
61
|
+
* @default 3
|
|
62
|
+
*/
|
|
63
|
+
halfOpenMax: number;
|
|
64
|
+
/**
|
|
65
|
+
* Optional callback when circuit state changes.
|
|
66
|
+
*/
|
|
67
|
+
onStateChange?: (from: CircuitState, to: CircuitState, name?: string) => void;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Error thrown when the circuit is open and calls are blocked.
|
|
71
|
+
*/
|
|
72
|
+
declare class CircuitOpenError extends Error {
|
|
73
|
+
readonly type: "CIRCUIT_OPEN";
|
|
74
|
+
readonly circuitName: string;
|
|
75
|
+
readonly state: CircuitState;
|
|
76
|
+
readonly retryAfterMs: number;
|
|
77
|
+
constructor(options: {
|
|
78
|
+
circuitName: string;
|
|
79
|
+
state: CircuitState;
|
|
80
|
+
retryAfterMs: number;
|
|
81
|
+
message?: string;
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Type guard for CircuitOpenError.
|
|
86
|
+
*/
|
|
87
|
+
declare function isCircuitOpenError(error: unknown): error is CircuitOpenError;
|
|
88
|
+
/**
|
|
89
|
+
* Circuit breaker statistics.
|
|
90
|
+
*/
|
|
91
|
+
interface CircuitBreakerStats {
|
|
92
|
+
state: CircuitState;
|
|
93
|
+
failureCount: number;
|
|
94
|
+
successCount: number;
|
|
95
|
+
lastFailureTime: number | null;
|
|
96
|
+
lastSuccessTime: number | null;
|
|
97
|
+
halfOpenSuccesses: number;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Circuit breaker instance for protecting external calls.
|
|
101
|
+
*/
|
|
102
|
+
interface CircuitBreaker {
|
|
103
|
+
/**
|
|
104
|
+
* Execute an operation with circuit breaker protection.
|
|
105
|
+
* Throws CircuitOpenError if the circuit is open.
|
|
106
|
+
*
|
|
107
|
+
* @param operation - The operation to execute
|
|
108
|
+
* @param options - Optional name for logging/metrics
|
|
109
|
+
* @returns The operation result
|
|
110
|
+
* @throws CircuitOpenError if circuit is open
|
|
111
|
+
*/
|
|
112
|
+
execute<T>(operation: () => T | Promise<T>, options?: {
|
|
113
|
+
name?: string;
|
|
114
|
+
}): Promise<T>;
|
|
115
|
+
/**
|
|
116
|
+
* Execute a Result-returning operation with circuit breaker protection.
|
|
117
|
+
* Returns a CircuitOpenError result instead of throwing.
|
|
118
|
+
*
|
|
119
|
+
* @param operation - The operation returning a Result
|
|
120
|
+
* @param options - Optional name for logging/metrics
|
|
121
|
+
* @returns Result with the value or CircuitOpenError
|
|
122
|
+
*/
|
|
123
|
+
executeResult<T, E>(operation: () => Result<T, E> | AsyncResult<T, E>, options?: {
|
|
124
|
+
name?: string;
|
|
125
|
+
}): AsyncResult<T, E | CircuitOpenError>;
|
|
126
|
+
/**
|
|
127
|
+
* Get current circuit state.
|
|
128
|
+
*/
|
|
129
|
+
getState(): CircuitState;
|
|
130
|
+
/**
|
|
131
|
+
* Get circuit breaker statistics.
|
|
132
|
+
*/
|
|
133
|
+
getStats(): CircuitBreakerStats;
|
|
134
|
+
/**
|
|
135
|
+
* Manually reset the circuit breaker to CLOSED state.
|
|
136
|
+
*/
|
|
137
|
+
reset(): void;
|
|
138
|
+
/**
|
|
139
|
+
* Manually open the circuit (for testing or manual intervention).
|
|
140
|
+
*/
|
|
141
|
+
forceOpen(): void;
|
|
142
|
+
/**
|
|
143
|
+
* Record a manual success (useful for health checks).
|
|
144
|
+
*/
|
|
145
|
+
recordSuccess(): void;
|
|
146
|
+
/**
|
|
147
|
+
* Record a manual failure (useful for health checks).
|
|
148
|
+
*/
|
|
149
|
+
recordFailure(error?: unknown): void;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Create a circuit breaker instance.
|
|
153
|
+
*
|
|
154
|
+
* @param name - Name for this circuit breaker (used in errors and logging)
|
|
155
|
+
* @param config - Configuration options
|
|
156
|
+
* @returns A CircuitBreaker instance
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```typescript
|
|
160
|
+
* const apiBreaker = createCircuitBreaker('external-api', {
|
|
161
|
+
* failureThreshold: 5,
|
|
162
|
+
* resetTimeout: 30000,
|
|
163
|
+
* });
|
|
164
|
+
*
|
|
165
|
+
* // In workflow
|
|
166
|
+
* const data = await apiBreaker.execute(() =>
|
|
167
|
+
* step(() => fetchFromApi(id))
|
|
168
|
+
* );
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
declare function createCircuitBreaker(name: string, config?: Partial<CircuitBreakerConfig>): CircuitBreaker;
|
|
172
|
+
/**
|
|
173
|
+
* Preset configurations for common use cases.
|
|
174
|
+
*/
|
|
175
|
+
declare const circuitBreakerPresets: {
|
|
176
|
+
/**
|
|
177
|
+
* Aggressive circuit breaker for critical paths.
|
|
178
|
+
* Opens quickly (3 failures) and recovers slowly (60s).
|
|
179
|
+
*/
|
|
180
|
+
readonly critical: {
|
|
181
|
+
failureThreshold: number;
|
|
182
|
+
resetTimeout: number;
|
|
183
|
+
windowSize: number;
|
|
184
|
+
halfOpenMax: number;
|
|
185
|
+
};
|
|
186
|
+
/**
|
|
187
|
+
* Standard circuit breaker for typical API calls.
|
|
188
|
+
* Balanced between stability and availability.
|
|
189
|
+
*/
|
|
190
|
+
readonly standard: {
|
|
191
|
+
failureThreshold: number;
|
|
192
|
+
resetTimeout: number;
|
|
193
|
+
windowSize: number;
|
|
194
|
+
halfOpenMax: number;
|
|
195
|
+
};
|
|
196
|
+
/**
|
|
197
|
+
* Lenient circuit breaker for non-critical operations.
|
|
198
|
+
* Opens slowly (10 failures) and recovers quickly (15s).
|
|
199
|
+
*/
|
|
200
|
+
readonly lenient: {
|
|
201
|
+
failureThreshold: number;
|
|
202
|
+
resetTimeout: number;
|
|
203
|
+
windowSize: number;
|
|
204
|
+
halfOpenMax: number;
|
|
205
|
+
};
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
export { type CircuitBreaker, type CircuitBreakerConfig, type CircuitBreakerStats, CircuitOpenError, type CircuitState, circuitBreakerPresets, createCircuitBreaker, isCircuitOpenError };
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { R as Result, A as AsyncResult } from './core-BuTBsR0x.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Circuit Breaker for Steps
|
|
5
|
+
*
|
|
6
|
+
* Prevents cascading failures by tracking step failure rates and
|
|
7
|
+
* short-circuiting calls when a threshold is exceeded.
|
|
8
|
+
*
|
|
9
|
+
* Uses the circuit breaker pattern with three states:
|
|
10
|
+
* - CLOSED: Normal operation (steps executing)
|
|
11
|
+
* - OPEN: Fast-fail mode (steps blocked)
|
|
12
|
+
* - HALF_OPEN: Testing if service recovered
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import { createCircuitBreaker } from 'awaitly';
|
|
17
|
+
*
|
|
18
|
+
* const breaker = createCircuitBreaker({
|
|
19
|
+
* failureThreshold: 5,
|
|
20
|
+
* resetTimeout: 30000,
|
|
21
|
+
* halfOpenMax: 3,
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* const result = await workflow(async (step) => {
|
|
25
|
+
* const data = await breaker.execute(
|
|
26
|
+
* () => step(() => callExternalApi()),
|
|
27
|
+
* { name: 'external-api' }
|
|
28
|
+
* );
|
|
29
|
+
* return data;
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Circuit breaker state.
|
|
36
|
+
*/
|
|
37
|
+
type CircuitState = "CLOSED" | "OPEN" | "HALF_OPEN";
|
|
38
|
+
/**
|
|
39
|
+
* Configuration for circuit breaker behavior.
|
|
40
|
+
*/
|
|
41
|
+
interface CircuitBreakerConfig {
|
|
42
|
+
/**
|
|
43
|
+
* Number of failures within the window before opening the circuit.
|
|
44
|
+
* @default 5
|
|
45
|
+
*/
|
|
46
|
+
failureThreshold: number;
|
|
47
|
+
/**
|
|
48
|
+
* Time in ms to wait before transitioning from OPEN to HALF_OPEN.
|
|
49
|
+
* @default 30000 (30 seconds)
|
|
50
|
+
*/
|
|
51
|
+
resetTimeout: number;
|
|
52
|
+
/**
|
|
53
|
+
* Time window in ms for counting failures.
|
|
54
|
+
* Failures older than this are discarded.
|
|
55
|
+
* @default 60000 (1 minute)
|
|
56
|
+
*/
|
|
57
|
+
windowSize: number;
|
|
58
|
+
/**
|
|
59
|
+
* Maximum number of test requests allowed in HALF_OPEN state.
|
|
60
|
+
* If all succeed, circuit closes. If any fail, circuit reopens.
|
|
61
|
+
* @default 3
|
|
62
|
+
*/
|
|
63
|
+
halfOpenMax: number;
|
|
64
|
+
/**
|
|
65
|
+
* Optional callback when circuit state changes.
|
|
66
|
+
*/
|
|
67
|
+
onStateChange?: (from: CircuitState, to: CircuitState, name?: string) => void;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Error thrown when the circuit is open and calls are blocked.
|
|
71
|
+
*/
|
|
72
|
+
declare class CircuitOpenError extends Error {
|
|
73
|
+
readonly type: "CIRCUIT_OPEN";
|
|
74
|
+
readonly circuitName: string;
|
|
75
|
+
readonly state: CircuitState;
|
|
76
|
+
readonly retryAfterMs: number;
|
|
77
|
+
constructor(options: {
|
|
78
|
+
circuitName: string;
|
|
79
|
+
state: CircuitState;
|
|
80
|
+
retryAfterMs: number;
|
|
81
|
+
message?: string;
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Type guard for CircuitOpenError.
|
|
86
|
+
*/
|
|
87
|
+
declare function isCircuitOpenError(error: unknown): error is CircuitOpenError;
|
|
88
|
+
/**
|
|
89
|
+
* Circuit breaker statistics.
|
|
90
|
+
*/
|
|
91
|
+
interface CircuitBreakerStats {
|
|
92
|
+
state: CircuitState;
|
|
93
|
+
failureCount: number;
|
|
94
|
+
successCount: number;
|
|
95
|
+
lastFailureTime: number | null;
|
|
96
|
+
lastSuccessTime: number | null;
|
|
97
|
+
halfOpenSuccesses: number;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Circuit breaker instance for protecting external calls.
|
|
101
|
+
*/
|
|
102
|
+
interface CircuitBreaker {
|
|
103
|
+
/**
|
|
104
|
+
* Execute an operation with circuit breaker protection.
|
|
105
|
+
* Throws CircuitOpenError if the circuit is open.
|
|
106
|
+
*
|
|
107
|
+
* @param operation - The operation to execute
|
|
108
|
+
* @param options - Optional name for logging/metrics
|
|
109
|
+
* @returns The operation result
|
|
110
|
+
* @throws CircuitOpenError if circuit is open
|
|
111
|
+
*/
|
|
112
|
+
execute<T>(operation: () => T | Promise<T>, options?: {
|
|
113
|
+
name?: string;
|
|
114
|
+
}): Promise<T>;
|
|
115
|
+
/**
|
|
116
|
+
* Execute a Result-returning operation with circuit breaker protection.
|
|
117
|
+
* Returns a CircuitOpenError result instead of throwing.
|
|
118
|
+
*
|
|
119
|
+
* @param operation - The operation returning a Result
|
|
120
|
+
* @param options - Optional name for logging/metrics
|
|
121
|
+
* @returns Result with the value or CircuitOpenError
|
|
122
|
+
*/
|
|
123
|
+
executeResult<T, E>(operation: () => Result<T, E> | AsyncResult<T, E>, options?: {
|
|
124
|
+
name?: string;
|
|
125
|
+
}): AsyncResult<T, E | CircuitOpenError>;
|
|
126
|
+
/**
|
|
127
|
+
* Get current circuit state.
|
|
128
|
+
*/
|
|
129
|
+
getState(): CircuitState;
|
|
130
|
+
/**
|
|
131
|
+
* Get circuit breaker statistics.
|
|
132
|
+
*/
|
|
133
|
+
getStats(): CircuitBreakerStats;
|
|
134
|
+
/**
|
|
135
|
+
* Manually reset the circuit breaker to CLOSED state.
|
|
136
|
+
*/
|
|
137
|
+
reset(): void;
|
|
138
|
+
/**
|
|
139
|
+
* Manually open the circuit (for testing or manual intervention).
|
|
140
|
+
*/
|
|
141
|
+
forceOpen(): void;
|
|
142
|
+
/**
|
|
143
|
+
* Record a manual success (useful for health checks).
|
|
144
|
+
*/
|
|
145
|
+
recordSuccess(): void;
|
|
146
|
+
/**
|
|
147
|
+
* Record a manual failure (useful for health checks).
|
|
148
|
+
*/
|
|
149
|
+
recordFailure(error?: unknown): void;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Create a circuit breaker instance.
|
|
153
|
+
*
|
|
154
|
+
* @param name - Name for this circuit breaker (used in errors and logging)
|
|
155
|
+
* @param config - Configuration options
|
|
156
|
+
* @returns A CircuitBreaker instance
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```typescript
|
|
160
|
+
* const apiBreaker = createCircuitBreaker('external-api', {
|
|
161
|
+
* failureThreshold: 5,
|
|
162
|
+
* resetTimeout: 30000,
|
|
163
|
+
* });
|
|
164
|
+
*
|
|
165
|
+
* // In workflow
|
|
166
|
+
* const data = await apiBreaker.execute(() =>
|
|
167
|
+
* step(() => fetchFromApi(id))
|
|
168
|
+
* );
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
declare function createCircuitBreaker(name: string, config?: Partial<CircuitBreakerConfig>): CircuitBreaker;
|
|
172
|
+
/**
|
|
173
|
+
* Preset configurations for common use cases.
|
|
174
|
+
*/
|
|
175
|
+
declare const circuitBreakerPresets: {
|
|
176
|
+
/**
|
|
177
|
+
* Aggressive circuit breaker for critical paths.
|
|
178
|
+
* Opens quickly (3 failures) and recovers slowly (60s).
|
|
179
|
+
*/
|
|
180
|
+
readonly critical: {
|
|
181
|
+
failureThreshold: number;
|
|
182
|
+
resetTimeout: number;
|
|
183
|
+
windowSize: number;
|
|
184
|
+
halfOpenMax: number;
|
|
185
|
+
};
|
|
186
|
+
/**
|
|
187
|
+
* Standard circuit breaker for typical API calls.
|
|
188
|
+
* Balanced between stability and availability.
|
|
189
|
+
*/
|
|
190
|
+
readonly standard: {
|
|
191
|
+
failureThreshold: number;
|
|
192
|
+
resetTimeout: number;
|
|
193
|
+
windowSize: number;
|
|
194
|
+
halfOpenMax: number;
|
|
195
|
+
};
|
|
196
|
+
/**
|
|
197
|
+
* Lenient circuit breaker for non-critical operations.
|
|
198
|
+
* Opens slowly (10 failures) and recovers quickly (15s).
|
|
199
|
+
*/
|
|
200
|
+
readonly lenient: {
|
|
201
|
+
failureThreshold: number;
|
|
202
|
+
resetTimeout: number;
|
|
203
|
+
windowSize: number;
|
|
204
|
+
halfOpenMax: number;
|
|
205
|
+
};
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
export { type CircuitBreaker, type CircuitBreakerConfig, type CircuitBreakerStats, CircuitOpenError, type CircuitState, circuitBreakerPresets, createCircuitBreaker, isCircuitOpenError };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var Q=e=>({ok:!0,value:e}),U=(e,p)=>({ok:!1,error:e,...p?.cause!==void 0?{cause:p.cause}:{}});var ae=e=>typeof e=="object"&&e!==null&&e.type==="UNEXPECTED_ERROR",W=Symbol.for("step_timeout_marker");function te(e){return typeof e!="object"||e===null?!1:e.type==="STEP_TIMEOUT"?!0:W in e}function ie(e){if(!(typeof e!="object"||e===null)){if(e.type==="STEP_TIMEOUT"){let p=e;return{timeoutMs:p.timeoutMs,stepName:p.stepName,stepKey:p.stepKey,attempt:p.attempt}}if(W in e)return e[W]}}var oe=Symbol("early-exit");function ce(e,p){return{[oe]:!0,error:e,meta:p}}function pe(e){return typeof e=="object"&&e!==null&&e[oe]===!0}var se=Symbol("mapper-exception");function le(e){return{[se]:!0,thrown:e}}function Ee(e){return typeof e=="object"&&e!==null&&e[se]===!0}function ye(e){return typeof e=="string"?{name:e}:e??{}}function Z(e,p){let{backoff:w,initialDelay:T,maxDelay:S,jitter:A}=p,k;switch(w){case"fixed":k=T;break;case"linear":k=T*e;break;case"exponential":k=T*Math.pow(2,e-1);break}if(k=Math.min(k,S),A){let o=k*.25*Math.random();k=k+o}return Math.floor(k)}function ee(e){return new Promise(p=>setTimeout(p,e))}var ne=Symbol("timeout");async function me(e,p,w){let T=new AbortController,S=p.error??{type:"STEP_TIMEOUT",stepName:w.name,stepKey:w.key,timeoutMs:p.ms,attempt:w.attempt},A,k=new Promise((g,f)=>{A=setTimeout(()=>{T.abort(),f({[ne]:!0,error:S})},p.ms)}),o;p.signal?o=Promise.resolve(e(T.signal)):o=Promise.resolve(e());try{return await Promise.race([o,k])}catch(g){if(typeof g=="object"&&g!==null&&g[ne]===!0){let f=g.error;if(typeof f=="object"&&f!==null&&f.type!=="STEP_TIMEOUT"){let D={timeoutMs:p.ms,stepName:w.name,stepKey:w.key,attempt:w.attempt};W in f?f[W]=D:Object.defineProperty(f,W,{value:D,enumerable:!1,writable:!0,configurable:!1})}throw f}throw g}finally{clearTimeout(A)}}var B={backoff:"exponential",initialDelay:100,maxDelay:3e4,jitter:!0,retryOn:()=>!0,onRetry:()=>{}};async function re(e,p){let{onError:w,onEvent:T,catchUnexpected:S,workflowId:A,context:k}=p&&typeof p=="object"?p:{},o=A??crypto.randomUUID(),g=!w&&!S,f=[],D=0,N=n=>n??`step_${++D}`,a=n=>{let h=n.context!==void 0||k===void 0?n:{...n,context:k};if(h.type==="step_success"){let K=h.stepId;for(let V=f.length-1;V>=0;V--){let $=f[V];if($.type==="race"&&!$.winnerId){$.winnerId=K;break}}}T?.(h,k)},b=ce,X=n=>pe(n),x=(n,h)=>g?h?.origin==="result"?{type:"UNEXPECTED_ERROR",cause:{type:"STEP_FAILURE",origin:"result",error:n,...h.resultCause!==void 0?{cause:h.resultCause}:{}}}:h?.origin==="throw"?{type:"UNEXPECTED_ERROR",cause:{type:"STEP_FAILURE",origin:"throw",error:n,thrown:h.thrown}}:{type:"UNEXPECTED_ERROR",cause:{type:"STEP_FAILURE",origin:"result",error:n}}:n,v=n=>({type:"UNEXPECTED_ERROR",cause:n.meta.origin==="result"?{type:"STEP_FAILURE",origin:"result",error:n.error,...n.meta.resultCause!==void 0?{cause:n.meta.resultCause}:{}}:{type:"STEP_FAILURE",origin:"throw",error:n.error,thrown:n.meta.thrown}});try{let h=function(E,u){let s=`scope_${Date.now()}_${Math.random().toString(36).slice(2,8)}`;return(async()=>{let t=performance.now(),r=!1;f.push({scopeId:s,type:"parallel"});let C=()=>{if(r)return;r=!0;let c=f.findIndex(i=>i.scopeId===s);c!==-1&&f.splice(c,1),a({type:"scope_end",workflowId:o,scopeId:s,ts:Date.now(),durationMs:performance.now()-t})};a({type:"scope_start",workflowId:o,scopeId:s,scopeType:"parallel",name:E,ts:Date.now()});try{let c=await u();if(C(),!c.ok)throw w?.(c.error,E,k),b(c.error,{origin:"result",resultCause:c.cause});return c.value}catch(c){throw C(),c}})()},K=function(E,u){let s=Object.keys(E),t=u.name??`Parallel(${s.join(", ")})`,r=`scope_${Date.now()}_${Math.random().toString(36).slice(2,8)}`;return(async()=>{let C=performance.now(),c=!1;f.push({scopeId:r,type:"parallel"});let i=()=>{if(c)return;c=!0;let y=f.findIndex(d=>d.scopeId===r);y!==-1&&f.splice(y,1),a({type:"scope_end",workflowId:o,scopeId:r,ts:Date.now(),durationMs:performance.now()-C})};a({type:"scope_start",workflowId:o,scopeId:r,scopeType:"parallel",name:t,ts:Date.now()});try{let y=await new Promise(O=>{if(s.length===0){O([]);return}let _=!1,R=s.length,Y=new Array(s.length);for(let I=0;I<s.length;I++){let L=s[I],q=I;Promise.resolve(E[L]()).catch(m=>U({type:"PROMISE_REJECTED",cause:m},{cause:{type:"PROMISE_REJECTION",reason:m}})).then(m=>{if(!_){if(!m.ok){_=!0,O([{key:L,result:m}]);return}Y[q]={key:L,result:m},R--,R===0&&O(Y)}})}});i();let d={};for(let{key:O,result:_}of y){if(!_.ok)throw w?.(_.error,O,k),b(_.error,{origin:"result",resultCause:_.cause});d[O]=_.value}return d}catch(y){throw i(),y}})()};var j=h,M=K;let n=(E,u)=>(async()=>{let s=ye(u),{name:t,key:r,retry:C,timeout:c}=s,i=N(r),y=T,d=y?performance.now():0;if(!(typeof E=="function")){if(C&&C.attempts>1)throw new Error("step: retry options require a function operation. Direct Promise/Result values cannot be re-executed on retry. Wrap your operation in a function: step(() => yourOperation, { retry: {...} })");if(c)throw new Error("step: timeout options require a function operation. Direct Promise/Result values cannot be wrapped with timeout after they've started. Wrap your operation in a function: step(() => yourOperation, { timeout: {...} })")}let R={attempts:Math.max(1,C?.attempts??1),backoff:C?.backoff??B.backoff,initialDelay:C?.initialDelay??B.initialDelay,maxDelay:C?.maxDelay??B.maxDelay,jitter:C?.jitter??B.jitter,retryOn:C?.retryOn??B.retryOn,onRetry:C?.onRetry??B.onRetry};T&&a({type:"step_start",workflowId:o,stepId:i,stepKey:r,name:t,ts:Date.now()});let Y;for(let m=1;m<=R.attempts;m++){let ue=y?performance.now():0;try{let l;if(typeof E=="function"?c?l=await me(E,c,{name:t,key:r,attempt:m}):l=await E():l=await E,l.ok){let F=performance.now()-d;return a({type:"step_success",workflowId:o,stepId:i,stepKey:r,name:t,ts:Date.now(),durationMs:F}),r&&a({type:"step_complete",workflowId:o,stepKey:r,name:t,ts:Date.now(),durationMs:F,result:l}),l.value}if(Y=l,m<R.attempts&&R.retryOn(l.error,m)){let F=Z(m,R);a({type:"step_retry",workflowId:o,stepId:i,stepKey:r,name:t,ts:Date.now(),attempt:m+1,maxAttempts:R.attempts,delayMs:F,error:l.error}),R.onRetry(l.error,m,F),await ee(F);continue}R.attempts>1&&a({type:"step_retries_exhausted",workflowId:o,stepId:i,stepKey:r,name:t,ts:Date.now(),durationMs:performance.now()-d,attempts:m,lastError:l.error});break}catch(l){let F=performance.now()-ue;if(X(l))throw a({type:"step_aborted",workflowId:o,stepId:i,stepKey:r,name:t,ts:Date.now(),durationMs:F}),l;if(te(l)){let P=ie(l),G=c?.ms??P?.timeoutMs??0;if(a({type:"step_timeout",workflowId:o,stepId:i,stepKey:r,name:t,ts:Date.now(),timeoutMs:G,attempt:m}),m<R.attempts&&R.retryOn(l,m)){let z=Z(m,R);a({type:"step_retry",workflowId:o,stepId:i,stepKey:r,name:t,ts:Date.now(),attempt:m+1,maxAttempts:R.attempts,delayMs:z,error:l}),R.onRetry(l,m,z),await ee(z);continue}R.attempts>1&&a({type:"step_retries_exhausted",workflowId:o,stepId:i,stepKey:r,name:t,ts:Date.now(),durationMs:performance.now()-d,attempts:m,lastError:l})}if(m<R.attempts&&R.retryOn(l,m)){let P=Z(m,R);a({type:"step_retry",workflowId:o,stepId:i,stepKey:r,name:t,ts:Date.now(),attempt:m+1,maxAttempts:R.attempts,delayMs:P,error:l}),R.onRetry(l,m,P),await ee(P);continue}R.attempts>1&&!te(l)&&a({type:"step_retries_exhausted",workflowId:o,stepId:i,stepKey:r,name:t,ts:Date.now(),durationMs:performance.now()-d,attempts:m,lastError:l});let H=performance.now()-d;if(S){let P;try{P=S(l)}catch(G){throw le(G)}throw a({type:"step_error",workflowId:o,stepId:i,stepKey:r,name:t,ts:Date.now(),durationMs:H,error:P}),r&&a({type:"step_complete",workflowId:o,stepKey:r,name:t,ts:Date.now(),durationMs:H,result:U(P,{cause:l}),meta:{origin:"throw",thrown:l}}),w?.(P,t,k),b(P,{origin:"throw",thrown:l})}else{let P={type:"UNEXPECTED_ERROR",cause:{type:"UNCAUGHT_EXCEPTION",thrown:l}};throw a({type:"step_error",workflowId:o,stepId:i,stepKey:r,name:t,ts:Date.now(),durationMs:H,error:P}),r&&a({type:"step_complete",workflowId:o,stepKey:r,name:t,ts:Date.now(),durationMs:H,result:U(P,{cause:l}),meta:{origin:"throw",thrown:l}}),l}}}let I=Y,L=performance.now()-d,q=x(I.error,{origin:"result",resultCause:I.cause});throw a({type:"step_error",workflowId:o,stepId:i,stepKey:r,name:t,ts:Date.now(),durationMs:L,error:q}),r&&a({type:"step_complete",workflowId:o,stepKey:r,name:t,ts:Date.now(),durationMs:L,result:I,meta:{origin:"result",resultCause:I.cause}}),w?.(I.error,t,k),b(I.error,{origin:"result",resultCause:I.cause})})();n.try=(E,u)=>{let s=u.name,t=u.key,r=N(t),C="error"in u?()=>u.error:u.onError,c=T;return(async()=>{let i=c?performance.now():0;T&&a({type:"step_start",workflowId:o,stepId:r,stepKey:t,name:s,ts:Date.now()});try{let y=await E(),d=performance.now()-i;return a({type:"step_success",workflowId:o,stepId:r,stepKey:t,name:s,ts:Date.now(),durationMs:d}),t&&a({type:"step_complete",workflowId:o,stepKey:t,name:s,ts:Date.now(),durationMs:d,result:Q(y)}),y}catch(y){let d=C(y),O=performance.now()-i,_=x(d,{origin:"throw",thrown:y});throw a({type:"step_error",workflowId:o,stepId:r,stepKey:t,name:s,ts:Date.now(),durationMs:O,error:_}),t&&a({type:"step_complete",workflowId:o,stepKey:t,name:s,ts:Date.now(),durationMs:O,result:U(d,{cause:y}),meta:{origin:"throw",thrown:y}}),w?.(d,s,k),b(d,{origin:"throw",thrown:y})}})()},n.fromResult=(E,u)=>{let s=u.name,t=u.key,r=N(t),C="error"in u?()=>u.error:u.onError,c=T;return(async()=>{let i=c?performance.now():0;T&&a({type:"step_start",workflowId:o,stepId:r,stepKey:t,name:s,ts:Date.now()});let y=await E();if(y.ok){let d=performance.now()-i;return a({type:"step_success",workflowId:o,stepId:r,stepKey:t,name:s,ts:Date.now(),durationMs:d}),t&&a({type:"step_complete",workflowId:o,stepKey:t,name:s,ts:Date.now(),durationMs:d,result:Q(y.value)}),y.value}else{let d=C(y.error),O=performance.now()-i,_=x(d,{origin:"result",resultCause:y.error});throw a({type:"step_error",workflowId:o,stepId:r,stepKey:t,name:s,ts:Date.now(),durationMs:O,error:_}),t&&a({type:"step_complete",workflowId:o,stepKey:t,name:s,ts:Date.now(),durationMs:O,result:U(d,{cause:y.error}),meta:{origin:"result",resultCause:y.error}}),w?.(d,s,k),b(d,{origin:"result",resultCause:y.error})}})()},n.retry=(E,u)=>n(E,{name:u.name,key:u.key,retry:{attempts:u.attempts,backoff:u.backoff,initialDelay:u.initialDelay,maxDelay:u.maxDelay,jitter:u.jitter,retryOn:u.retryOn,onRetry:u.onRetry},timeout:u.timeout}),n.withTimeout=(E,u)=>n(E,{name:u.name,key:u.key,timeout:u}),n.parallel=((...E)=>{if(typeof E[0]=="string"){let u=E[0],s=E[1];return h(u,s)}else{let u=E[0],s=E[1]??{};return K(u,s)}}),n.race=(E,u)=>{let s=`scope_${Date.now()}_${Math.random().toString(36).slice(2,8)}`;return(async()=>{let t=performance.now(),r=!1,C={scopeId:s,type:"race",winnerId:void 0};f.push(C);let c=()=>{if(r)return;r=!0;let i=f.findIndex(y=>y.scopeId===s);i!==-1&&f.splice(i,1),a({type:"scope_end",workflowId:o,scopeId:s,ts:Date.now(),durationMs:performance.now()-t,winnerId:C.winnerId})};a({type:"scope_start",workflowId:o,scopeId:s,scopeType:"race",name:E,ts:Date.now()});try{let i=await u();if(c(),!i.ok)throw w?.(i.error,E,k),b(i.error,{origin:"result",resultCause:i.cause});return i.value}catch(i){throw c(),i}})()},n.allSettled=(E,u)=>{let s=`scope_${Date.now()}_${Math.random().toString(36).slice(2,8)}`;return(async()=>{let t=performance.now(),r=!1;f.push({scopeId:s,type:"allSettled"});let C=()=>{if(r)return;r=!0;let c=f.findIndex(i=>i.scopeId===s);c!==-1&&f.splice(c,1),a({type:"scope_end",workflowId:o,scopeId:s,ts:Date.now(),durationMs:performance.now()-t})};a({type:"scope_start",workflowId:o,scopeId:s,scopeType:"allSettled",name:E,ts:Date.now()});try{let c=await u();if(C(),!c.ok)throw w?.(c.error,E,k),b(c.error,{origin:"result",resultCause:c.cause});return c.value}catch(c){throw C(),c}})()};let $=await e(n);return Q($)}catch(n){if(Ee(n))throw n.thrown;if(X(n)){let K=n.meta.origin==="throw"?n.meta.thrown:n.meta.resultCause;if(S||w)return U(n.error,{cause:K});if(ae(n.error))return U(n.error,{cause:K});let V=v(n);return U(V,{cause:K})}if(S){let K=S(n);return w?.(K,"unexpected",k),U(K,{cause:n})}let h={type:"UNEXPECTED_ERROR",cause:{type:"UNCAUGHT_EXCEPTION",thrown:n}};return w?.(h,"unexpected",k),U(h,{cause:n})}}re.strict=(e,p)=>re(e,p);var J=class extends Error{type="CIRCUIT_OPEN";circuitName;state;retryAfterMs;constructor(p){super(p.message??`Circuit breaker "${p.circuitName}" is ${p.state}. Retry after ${Math.ceil(p.retryAfterMs/1e3)}s`),this.name="CircuitOpenError",this.circuitName=p.circuitName,this.state=p.state,this.retryAfterMs=p.retryAfterMs}};function we(e){return typeof e=="object"&&e!==null&&e.type==="CIRCUIT_OPEN"}var Te={failureThreshold:5,resetTimeout:3e4,windowSize:6e4,halfOpenMax:3};function ke(e,p){let w={...Te,...p},T="CLOSED",S=[],A=null,k=null,o=0,g=0;function f(){let x=Date.now();S=S.filter(v=>x-v.timestamp<w.windowSize)}function D(x){if(T!==x){let v=T;T=x,x==="HALF_OPEN"&&(g=0),w.onStateChange?.(v,x,e)}}function N(){return T!=="OPEN"||A===null?!1:Date.now()-A>=w.resetTimeout?(D("HALF_OPEN"),!0):!1}function a(){k=Date.now(),o++,T==="HALF_OPEN"&&(g++,g>=w.halfOpenMax&&(D("CLOSED"),S=[]))}function b(x){let v=Date.now();A=v,f(),S.push({timestamp:v,error:x}),(T==="HALF_OPEN"||T==="CLOSED"&&S.length>=w.failureThreshold)&&D("OPEN")}function X(){if(T==="CLOSED")return 0;if(T==="OPEN"){if(N())return 0;let x=Date.now(),v=A?x-A:0;return Math.max(0,w.resetTimeout-v)}return 0}return{async execute(x,v){let j=X();if(j>0)throw new J({circuitName:e,state:T,retryAfterMs:j});try{let M=await x();return a(),M}catch(M){throw b(M),M}},async executeResult(x,v){let j=X();if(j>0)return U(new J({circuitName:e,state:T,retryAfterMs:j}));try{let M=await x();return M.ok?a():b(M.error),M}catch(M){throw b(M),M}},getState(){return T==="OPEN"&&N(),T},getStats(){return f(),{state:this.getState(),failureCount:S.length,successCount:o,lastFailureTime:A,lastSuccessTime:k,halfOpenSuccesses:g}},reset(){D("CLOSED"),S=[],g=0},forceOpen(){A=Date.now(),D("OPEN")},recordSuccess(){a()},recordFailure(x){b(x??new Error("Manual failure"))}}}var fe={critical:{failureThreshold:3,resetTimeout:6e4,windowSize:3e4,halfOpenMax:1},standard:{failureThreshold:5,resetTimeout:3e4,windowSize:6e4,halfOpenMax:3},lenient:{failureThreshold:10,resetTimeout:15e3,windowSize:12e4,halfOpenMax:5}};export{J as CircuitOpenError,fe as circuitBreakerPresets,ke as createCircuitBreaker,we as isCircuitOpenError};
|
|
2
|
+
//# sourceMappingURL=circuit-breaker.js.map
|