flowshield 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/CHANGELOG.md +30 -0
- package/LICENSE +21 -0
- package/README.md +272 -0
- package/dist/cjs/compose.js +53 -0
- package/dist/cjs/compose.js.map +1 -0
- package/dist/cjs/index.js +32 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/policies/bulkhead.js +75 -0
- package/dist/cjs/policies/bulkhead.js.map +1 -0
- package/dist/cjs/policies/cache.js +99 -0
- package/dist/cjs/policies/cache.js.map +1 -0
- package/dist/cjs/policies/circuit-breaker.js +110 -0
- package/dist/cjs/policies/circuit-breaker.js.map +1 -0
- package/dist/cjs/policies/fallback.js +33 -0
- package/dist/cjs/policies/fallback.js.map +1 -0
- package/dist/cjs/policies/hedge.js +74 -0
- package/dist/cjs/policies/hedge.js.map +1 -0
- package/dist/cjs/policies/rate-limiter.js +92 -0
- package/dist/cjs/policies/rate-limiter.js.map +1 -0
- package/dist/cjs/policies/retry.js +61 -0
- package/dist/cjs/policies/retry.js.map +1 -0
- package/dist/cjs/policies/timeout.js +45 -0
- package/dist/cjs/policies/timeout.js.map +1 -0
- package/dist/cjs/types.js +55 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/cjs/utils.js +84 -0
- package/dist/cjs/utils.js.map +1 -0
- package/dist/esm/compose.js +49 -0
- package/dist/esm/compose.js.map +1 -0
- package/dist/esm/index.js +13 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/policies/bulkhead.js +72 -0
- package/dist/esm/policies/bulkhead.js.map +1 -0
- package/dist/esm/policies/cache.js +96 -0
- package/dist/esm/policies/cache.js.map +1 -0
- package/dist/esm/policies/circuit-breaker.js +107 -0
- package/dist/esm/policies/circuit-breaker.js.map +1 -0
- package/dist/esm/policies/fallback.js +30 -0
- package/dist/esm/policies/fallback.js.map +1 -0
- package/dist/esm/policies/hedge.js +71 -0
- package/dist/esm/policies/hedge.js.map +1 -0
- package/dist/esm/policies/rate-limiter.js +89 -0
- package/dist/esm/policies/rate-limiter.js.map +1 -0
- package/dist/esm/policies/retry.js +58 -0
- package/dist/esm/policies/retry.js.map +1 -0
- package/dist/esm/policies/timeout.js +42 -0
- package/dist/esm/policies/timeout.js.map +1 -0
- package/dist/esm/types.js +46 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/utils.js +77 -0
- package/dist/esm/utils.js.map +1 -0
- package/dist/types/compose.d.ts +32 -0
- package/dist/types/compose.d.ts.map +1 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/policies/bulkhead.d.ts +20 -0
- package/dist/types/policies/bulkhead.d.ts.map +1 -0
- package/dist/types/policies/cache.d.ts +19 -0
- package/dist/types/policies/cache.d.ts.map +1 -0
- package/dist/types/policies/circuit-breaker.d.ts +19 -0
- package/dist/types/policies/circuit-breaker.d.ts.map +1 -0
- package/dist/types/policies/fallback.d.ts +14 -0
- package/dist/types/policies/fallback.d.ts.map +1 -0
- package/dist/types/policies/hedge.d.ts +15 -0
- package/dist/types/policies/hedge.d.ts.map +1 -0
- package/dist/types/policies/rate-limiter.d.ts +19 -0
- package/dist/types/policies/rate-limiter.d.ts.map +1 -0
- package/dist/types/policies/retry.d.ts +14 -0
- package/dist/types/policies/retry.d.ts.map +1 -0
- package/dist/types/policies/timeout.d.ts +15 -0
- package/dist/types/policies/timeout.d.ts.map +1 -0
- package/dist/types/types.d.ts +133 -0
- package/dist/types/types.d.ts.map +1 -0
- package/dist/types/utils.d.ts +21 -0
- package/dist/types/utils.d.ts.map +1 -0
- package/package.json +80 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [1.0.0] - 2026-03-30
|
|
4
|
+
|
|
5
|
+
### 🎉 Initial Release
|
|
6
|
+
|
|
7
|
+
#### Policies
|
|
8
|
+
- **Retry** — Exponential, linear, constant, and decorrelated jitter backoff strategies. Supports `shouldRetry` predicates, `onRetry` callbacks, and `AbortSignal` cancellation.
|
|
9
|
+
- **Circuit Breaker** — Closed/open/half-open state machine with configurable failure/success thresholds, reset timeout, and event callbacks (`onOpen`, `onClose`, `onHalfOpen`).
|
|
10
|
+
- **Timeout** — AbortSignal-based timeout with configurable deadline in milliseconds.
|
|
11
|
+
- **Bulkhead** — Semaphore-based concurrency limiter with configurable max concurrent executions and queue size.
|
|
12
|
+
- **Fallback** — Type-safe fallback values or async fallback functions with `shouldFallback` predicate.
|
|
13
|
+
- **Rate Limiter** — Token bucket algorithm with configurable tokens per interval, reject-on-limit mode, and manual reset.
|
|
14
|
+
- **Hedge** — Hedged requests pattern — sends a parallel request after a configurable delay; first to resolve wins.
|
|
15
|
+
- **Cache** — TTL-based memoization with stale-while-revalidate, custom key generation, and eviction callbacks.
|
|
16
|
+
|
|
17
|
+
#### Composition
|
|
18
|
+
- **`pipe(...policies)`** — Compose policies left-to-right (outermost first).
|
|
19
|
+
- **`wrap(outer, inner)`** — Nest an inner policy within an outer policy.
|
|
20
|
+
|
|
21
|
+
#### Error Types
|
|
22
|
+
- `FlowShieldError` — Base error class with cause support.
|
|
23
|
+
- `TimeoutError`, `CircuitOpenError`, `BulkheadRejectedError`, `RateLimitExceededError`, `RetryExhaustedError`.
|
|
24
|
+
|
|
25
|
+
#### Infrastructure
|
|
26
|
+
- Zero dependencies.
|
|
27
|
+
- Dual ESM/CJS output with TypeScript declarations.
|
|
28
|
+
- 100% test coverage (statements, branches, functions, lines).
|
|
29
|
+
- Tree-shakable — only import what you use.
|
|
30
|
+
- Node.js ≥ 18.0.0, compatible with Bun, Deno, and edge runtimes.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Avinash Velu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
# FlowShield
|
|
2
|
+
|
|
3
|
+
> **Zero-dependency, TypeScript-first resilience & fault-tolerance toolkit.**
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/flowshield)
|
|
6
|
+
[](https://www.npmjs.com/package/flowshield)
|
|
7
|
+
[](https://github.com/Avinashvelu03/flowshield/blob/main/LICENSE)
|
|
8
|
+
[](https://www.typescriptlang.org/)
|
|
9
|
+
[](https://github.com/Avinashvelu03/flowshield)
|
|
10
|
+
|
|
11
|
+
Composable fault-tolerance policies for any async operation. Ship resilient microservices, API clients, and distributed systems with **retry**, **circuit breaker**, **timeout**, **bulkhead**, **fallback**, **rate limiter**, **hedge**, and **cache** — all in a lightweight, tree-shakable package.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## ✨ Features
|
|
16
|
+
|
|
17
|
+
- 🔁 **Retry** — Exponential backoff, jitter, configurable strategies
|
|
18
|
+
- ⚡ **Circuit Breaker** — Half-open/open/closed with auto-recovery
|
|
19
|
+
- ⏱️ **Timeout** — AbortSignal-based deadlines
|
|
20
|
+
- 🚧 **Bulkhead** — Concurrency limiting (semaphore pattern)
|
|
21
|
+
- 🛡️ **Fallback** — Type-safe fallback chains
|
|
22
|
+
- 🚦 **Rate Limiter** — Token bucket algorithm
|
|
23
|
+
- 🏎️ **Hedge** — Hedged (parallel) requests for latency reduction
|
|
24
|
+
- 💾 **Cache** — TTL-based memoization with stale-while-revalidate
|
|
25
|
+
- 🔗 **Composable** — Chain any policies together with `pipe()` or `wrap()`
|
|
26
|
+
- 📦 **Zero dependencies** — No supply chain risk
|
|
27
|
+
- 🌳 **Tree-shakable** — Only pay for what you use
|
|
28
|
+
- 🌐 **Edge-ready** — Works in Node.js, Bun, Deno, Cloudflare Workers
|
|
29
|
+
- 🎯 **100% test coverage** — Statements, branches, functions, lines
|
|
30
|
+
|
|
31
|
+
## 📦 Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install flowshield
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
yarn add flowshield
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pnpm add flowshield
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 🚀 Quick Start
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { retry, circuitBreaker, timeout, pipe } from 'flowshield';
|
|
49
|
+
|
|
50
|
+
// Simple retry with exponential backoff
|
|
51
|
+
const data = await retry({ maxAttempts: 3, backoff: 'exponential' })(
|
|
52
|
+
() => fetch('/api/data').then(r => r.json()),
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// Compose multiple policies
|
|
56
|
+
const resilientFetch = pipe(
|
|
57
|
+
timeout({ ms: 5000 }),
|
|
58
|
+
retry({ maxAttempts: 3, backoff: 'exponential' }),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const result = await resilientFetch(() => fetch('/api/data'));
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 📖 API Reference
|
|
67
|
+
|
|
68
|
+
### Retry
|
|
69
|
+
|
|
70
|
+
Re-executes a failed operation with configurable backoff strategies.
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { retry } from 'flowshield';
|
|
74
|
+
|
|
75
|
+
const result = await retry({
|
|
76
|
+
maxAttempts: 5, // Default: 3
|
|
77
|
+
delay: 200, // Base delay in ms. Default: 200
|
|
78
|
+
backoff: 'exponential', // 'constant' | 'linear' | 'exponential' | 'decorrelatedJitter'
|
|
79
|
+
maxDelay: 30000, // Cap delay at 30s. Default: 30000
|
|
80
|
+
shouldRetry: (err, attempt) => attempt < 3, // Conditional retry
|
|
81
|
+
onRetry: (err, attempt, delay) => console.log(`Retry ${attempt} in ${delay}ms`),
|
|
82
|
+
signal: controller.signal, // AbortSignal for cancellation
|
|
83
|
+
})(
|
|
84
|
+
() => fetch('/api/data'),
|
|
85
|
+
);
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Circuit Breaker
|
|
89
|
+
|
|
90
|
+
Prevents calls to a failing service, allowing it time to recover.
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { circuitBreaker } from 'flowshield';
|
|
94
|
+
|
|
95
|
+
const cb = circuitBreaker({
|
|
96
|
+
failureThreshold: 5, // Failures before opening. Default: 5
|
|
97
|
+
successThreshold: 1, // Successes in half-open to close. Default: 1
|
|
98
|
+
resetTimeout: 30000, // Time in open state before half-open. Default: 30000
|
|
99
|
+
onOpen: () => console.log('Circuit opened!'),
|
|
100
|
+
onClose: () => console.log('Circuit closed!'),
|
|
101
|
+
onHalfOpen: () => console.log('Circuit half-open...'),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const result = await cb.execute(() => fetch('/api/health'));
|
|
105
|
+
console.log(cb.handle.state); // 'closed' | 'open' | 'half-open'
|
|
106
|
+
cb.handle.reset(); // Manually reset
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Timeout
|
|
110
|
+
|
|
111
|
+
Rejects if an operation doesn't complete within a deadline.
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import { timeout } from 'flowshield';
|
|
115
|
+
|
|
116
|
+
const result = await timeout({ ms: 5000 })(
|
|
117
|
+
() => fetch('/api/slow-endpoint'),
|
|
118
|
+
);
|
|
119
|
+
// Throws TimeoutError if not resolved within 5 seconds
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Bulkhead
|
|
123
|
+
|
|
124
|
+
Limits concurrent executions to prevent resource exhaustion.
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
import { bulkhead } from 'flowshield';
|
|
128
|
+
|
|
129
|
+
const bh = bulkhead({
|
|
130
|
+
maxConcurrent: 10, // Max parallel executions. Default: 10
|
|
131
|
+
maxQueue: 100, // Max queued requests. Default: 0 (no queue)
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const result = await bh.execute(() => fetch('/api/data'));
|
|
135
|
+
console.log(bh.handle.running); // Current active count
|
|
136
|
+
console.log(bh.handle.queued); // Current queue size
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Fallback
|
|
140
|
+
|
|
141
|
+
Returns a fallback value when an operation fails.
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import { fallback } from 'flowshield';
|
|
145
|
+
|
|
146
|
+
// Static fallback
|
|
147
|
+
const result = await fallback({ fallback: [] })(
|
|
148
|
+
() => fetch('/api/items').then(r => r.json()),
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// Dynamic fallback
|
|
152
|
+
const result2 = await fallback({
|
|
153
|
+
fallback: async (error) => fetchFromCache(error),
|
|
154
|
+
shouldFallback: (err) => err instanceof NetworkError,
|
|
155
|
+
})(
|
|
156
|
+
() => fetch('/api/data'),
|
|
157
|
+
);
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Rate Limiter
|
|
161
|
+
|
|
162
|
+
Controls how many operations can execute within a time window.
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
import { rateLimiter } from 'flowshield';
|
|
166
|
+
|
|
167
|
+
const rl = rateLimiter({
|
|
168
|
+
tokensPerInterval: 10, // Requests per interval. Default: 10
|
|
169
|
+
interval: 1000, // Interval in ms. Default: 1000
|
|
170
|
+
rejectOnLimit: false, // Queue or reject. Default: false (queue)
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const result = await rl.execute(() => fetch('/api/data'));
|
|
174
|
+
console.log(rl.handle.availableTokens); // Check remaining tokens
|
|
175
|
+
rl.handle.reset(); // Reset the bucket
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Hedge
|
|
179
|
+
|
|
180
|
+
Sends a parallel request if the primary is too slow — first to resolve wins.
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
import { hedge } from 'flowshield';
|
|
184
|
+
|
|
185
|
+
const result = await hedge({ hedgeDelay: 2000 })(
|
|
186
|
+
() => fetch('/api/data').then(r => r.json()),
|
|
187
|
+
);
|
|
188
|
+
// If primary doesn't resolve in 2s, a second request is sent.
|
|
189
|
+
// The fastest response wins.
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Cache
|
|
193
|
+
|
|
194
|
+
Memoizes async operation results with TTL and stale-while-revalidate support.
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
import { cache } from 'flowshield';
|
|
198
|
+
|
|
199
|
+
const c = cache({
|
|
200
|
+
ttl: 60000, // Time-to-live in ms. Default: 60000
|
|
201
|
+
staleWhileRevalidate: true, // Return stale, refresh in background
|
|
202
|
+
keyFn: () => 'custom-key', // Custom cache key
|
|
203
|
+
onEvict: (key, value) => {}, // Eviction callback
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const result = await c.execute(() => fetch('/api/data').then(r => r.json()));
|
|
207
|
+
c.handle.invalidate('custom-key'); // Invalidate specific key
|
|
208
|
+
c.handle.clear(); // Clear all
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Composing Policies
|
|
212
|
+
|
|
213
|
+
Combine multiple policies with `pipe()` (left-to-right) or `wrap()`.
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
import { pipe, wrap, retry, timeout, circuitBreaker, fallback } from 'flowshield';
|
|
217
|
+
|
|
218
|
+
// pipe: policies applied left-to-right (outermost first)
|
|
219
|
+
const resilient = pipe(
|
|
220
|
+
timeout({ ms: 10000 }),
|
|
221
|
+
retry({ maxAttempts: 3, backoff: 'exponential' }),
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
const data = await resilient(() => fetch('/api/data'));
|
|
225
|
+
|
|
226
|
+
// wrap: explicit outer/inner nesting
|
|
227
|
+
const policy = wrap(
|
|
228
|
+
timeout({ ms: 5000 }),
|
|
229
|
+
retry({ maxAttempts: 3 }),
|
|
230
|
+
);
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## 🛡️ Error Types
|
|
236
|
+
|
|
237
|
+
FlowShield provides typed errors for each failure mode:
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
import {
|
|
241
|
+
FlowShieldError, // Base error class
|
|
242
|
+
TimeoutError, // Operation timed out
|
|
243
|
+
CircuitOpenError, // Circuit breaker is open
|
|
244
|
+
BulkheadRejectedError, // Bulkhead capacity exceeded
|
|
245
|
+
RateLimitExceededError, // Rate limit exceeded
|
|
246
|
+
RetryExhaustedError, // All retry attempts failed
|
|
247
|
+
} from 'flowshield';
|
|
248
|
+
|
|
249
|
+
try {
|
|
250
|
+
await resilientFetch(() => fetch('/api'));
|
|
251
|
+
} catch (error) {
|
|
252
|
+
if (error instanceof TimeoutError) {
|
|
253
|
+
// Handle timeout
|
|
254
|
+
} else if (error instanceof CircuitOpenError) {
|
|
255
|
+
// Handle circuit open
|
|
256
|
+
} else if (error instanceof RetryExhaustedError) {
|
|
257
|
+
console.log(`Failed after ${error.attempts} attempts`);
|
|
258
|
+
console.log(`Last error:`, error.cause);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## 🔧 Requirements
|
|
266
|
+
|
|
267
|
+
- **Node.js** ≥ 18.0.0
|
|
268
|
+
- **TypeScript** ≥ 5.0 (for type inference)
|
|
269
|
+
|
|
270
|
+
## 📄 License
|
|
271
|
+
|
|
272
|
+
[MIT](./LICENSE) © [Avinash Velu](https://github.com/Avinashvelu03)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.pipe = pipe;
|
|
4
|
+
exports.wrap = wrap;
|
|
5
|
+
/**
|
|
6
|
+
* Composes multiple policies into a single policy.
|
|
7
|
+
* Policies are applied **left-to-right** (outermost first).
|
|
8
|
+
*
|
|
9
|
+
* `pipe(a, b, c)(fn)` is equivalent to `a(()=> b(()=> c(fn)))`.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const resilient = pipe(
|
|
14
|
+
* timeout({ ms: 5000 }),
|
|
15
|
+
* retry({ maxAttempts: 3 }),
|
|
16
|
+
* );
|
|
17
|
+
* const result = await resilient(() => fetch('/api'));
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
function pipe(...policies) {
|
|
21
|
+
if (policies.length === 0) {
|
|
22
|
+
return (fn) => fn();
|
|
23
|
+
}
|
|
24
|
+
return (fn) => {
|
|
25
|
+
// Build from right to left: the innermost policy wraps fn directly
|
|
26
|
+
let composed = fn;
|
|
27
|
+
for (let i = policies.length - 1; i >= 0; i--) {
|
|
28
|
+
const policy = policies[i];
|
|
29
|
+
const next = composed;
|
|
30
|
+
composed = () => policy(next);
|
|
31
|
+
}
|
|
32
|
+
return composed();
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Wraps an inner policy with an outer policy.
|
|
37
|
+
* `wrap(outer, inner)(fn)` is equivalent to `outer(() => inner(fn))`.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* const policy = wrap(
|
|
42
|
+
* timeout({ ms: 5000 }),
|
|
43
|
+
* retry({ maxAttempts: 3 }),
|
|
44
|
+
* );
|
|
45
|
+
* const result = await policy(() => fetch('/api'));
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
function wrap(outer, inner) {
|
|
49
|
+
return (fn) => {
|
|
50
|
+
return outer(() => inner(fn));
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=compose.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose.js","sourceRoot":"","sources":["../../src/compose.ts"],"names":[],"mappings":";;AAiBA,oBAiBC;AAeD,oBAIC;AAnDD;;;;;;;;;;;;;;GAcG;AACH,SAAgB,IAAI,CAAC,GAAG,QAAkB;IACxC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,CAAI,EAAoB,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;IAC3C,CAAC;IAED,OAAO,CAAI,EAAoB,EAAc,EAAE;QAC7C,mEAAmE;QACnE,IAAI,QAAQ,GAAqB,EAAE,CAAC;QAEpC,KAAK,IAAI,CAAC,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC9C,MAAM,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;YAC3B,MAAM,IAAI,GAAG,QAAQ,CAAC;YACtB,QAAQ,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAChC,CAAC;QAED,OAAO,QAAQ,EAAE,CAAC;IACpB,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,SAAgB,IAAI,CAAC,KAAa,EAAE,KAAa;IAC/C,OAAO,CAAI,EAAoB,EAAc,EAAE;QAC7C,OAAO,KAAK,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;IAChC,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RetryExhaustedError = exports.RateLimitExceededError = exports.BulkheadRejectedError = exports.CircuitOpenError = exports.TimeoutError = exports.FlowShieldError = exports.wrap = exports.pipe = exports.cache = exports.hedge = exports.rateLimiter = exports.fallback = exports.bulkhead = exports.timeout = exports.circuitBreaker = exports.retry = void 0;
|
|
4
|
+
// ─── Policies ───────────────────────────────────────────────────────────────
|
|
5
|
+
var retry_js_1 = require("./policies/retry.js");
|
|
6
|
+
Object.defineProperty(exports, "retry", { enumerable: true, get: function () { return retry_js_1.retry; } });
|
|
7
|
+
var circuit_breaker_js_1 = require("./policies/circuit-breaker.js");
|
|
8
|
+
Object.defineProperty(exports, "circuitBreaker", { enumerable: true, get: function () { return circuit_breaker_js_1.circuitBreaker; } });
|
|
9
|
+
var timeout_js_1 = require("./policies/timeout.js");
|
|
10
|
+
Object.defineProperty(exports, "timeout", { enumerable: true, get: function () { return timeout_js_1.timeout; } });
|
|
11
|
+
var bulkhead_js_1 = require("./policies/bulkhead.js");
|
|
12
|
+
Object.defineProperty(exports, "bulkhead", { enumerable: true, get: function () { return bulkhead_js_1.bulkhead; } });
|
|
13
|
+
var fallback_js_1 = require("./policies/fallback.js");
|
|
14
|
+
Object.defineProperty(exports, "fallback", { enumerable: true, get: function () { return fallback_js_1.fallback; } });
|
|
15
|
+
var rate_limiter_js_1 = require("./policies/rate-limiter.js");
|
|
16
|
+
Object.defineProperty(exports, "rateLimiter", { enumerable: true, get: function () { return rate_limiter_js_1.rateLimiter; } });
|
|
17
|
+
var hedge_js_1 = require("./policies/hedge.js");
|
|
18
|
+
Object.defineProperty(exports, "hedge", { enumerable: true, get: function () { return hedge_js_1.hedge; } });
|
|
19
|
+
var cache_js_1 = require("./policies/cache.js");
|
|
20
|
+
Object.defineProperty(exports, "cache", { enumerable: true, get: function () { return cache_js_1.cache; } });
|
|
21
|
+
// ─── Composition ────────────────────────────────────────────────────────────
|
|
22
|
+
var compose_js_1 = require("./compose.js");
|
|
23
|
+
Object.defineProperty(exports, "pipe", { enumerable: true, get: function () { return compose_js_1.pipe; } });
|
|
24
|
+
Object.defineProperty(exports, "wrap", { enumerable: true, get: function () { return compose_js_1.wrap; } });
|
|
25
|
+
var types_js_1 = require("./types.js");
|
|
26
|
+
Object.defineProperty(exports, "FlowShieldError", { enumerable: true, get: function () { return types_js_1.FlowShieldError; } });
|
|
27
|
+
Object.defineProperty(exports, "TimeoutError", { enumerable: true, get: function () { return types_js_1.TimeoutError; } });
|
|
28
|
+
Object.defineProperty(exports, "CircuitOpenError", { enumerable: true, get: function () { return types_js_1.CircuitOpenError; } });
|
|
29
|
+
Object.defineProperty(exports, "BulkheadRejectedError", { enumerable: true, get: function () { return types_js_1.BulkheadRejectedError; } });
|
|
30
|
+
Object.defineProperty(exports, "RateLimitExceededError", { enumerable: true, get: function () { return types_js_1.RateLimitExceededError; } });
|
|
31
|
+
Object.defineProperty(exports, "RetryExhaustedError", { enumerable: true, get: function () { return types_js_1.RetryExhaustedError; } });
|
|
32
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAAA,+EAA+E;AAC/E,gDAA4C;AAAnC,iGAAA,KAAK,OAAA;AACd,oEAA+D;AAAtD,oHAAA,cAAc,OAAA;AACvB,oDAAgD;AAAvC,qGAAA,OAAO,OAAA;AAChB,sDAAkD;AAAzC,uGAAA,QAAQ,OAAA;AACjB,sDAAkD;AAAzC,uGAAA,QAAQ,OAAA;AACjB,8DAAyD;AAAhD,8GAAA,WAAW,OAAA;AACpB,gDAA4C;AAAnC,iGAAA,KAAK,OAAA;AACd,gDAA4C;AAAnC,iGAAA,KAAK,OAAA;AAEd,+EAA+E;AAC/E,2CAA0C;AAAjC,kGAAA,IAAI,OAAA;AAAE,kGAAA,IAAI,OAAA;AAqBnB,uCAOoB;AANlB,2GAAA,eAAe,OAAA;AACf,wGAAA,YAAY,OAAA;AACZ,4GAAA,gBAAgB,OAAA;AAChB,iHAAA,qBAAqB,OAAA;AACrB,kHAAA,sBAAsB,OAAA;AACtB,+GAAA,mBAAmB,OAAA"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.bulkhead = bulkhead;
|
|
4
|
+
const types_js_1 = require("../types.js");
|
|
5
|
+
const utils_js_1 = require("../utils.js");
|
|
6
|
+
const DEFAULTS = {
|
|
7
|
+
maxConcurrent: 10,
|
|
8
|
+
maxQueue: 0,
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Creates a bulkhead (concurrency limiter) policy that limits the number
|
|
12
|
+
* of concurrent executions of the wrapped operation.
|
|
13
|
+
*
|
|
14
|
+
* Returns an object with `execute` (the policy function) and `handle`
|
|
15
|
+
* (an interface to inspect the bulkhead's running / queued counts).
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* const bh = bulkhead({ maxConcurrent: 5, maxQueue: 10 });
|
|
20
|
+
* const result = await bh.execute(() => fetch('/api/data'));
|
|
21
|
+
* console.log(bh.handle.running); // number of active calls
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
function bulkhead(options = {}) {
|
|
25
|
+
const maxConcurrent = options.maxConcurrent ?? DEFAULTS.maxConcurrent;
|
|
26
|
+
const maxQueue = options.maxQueue ?? DEFAULTS.maxQueue;
|
|
27
|
+
(0, utils_js_1.assertPositiveInteger)(maxConcurrent, 'maxConcurrent');
|
|
28
|
+
(0, utils_js_1.assertNonNegative)(maxQueue, 'maxQueue');
|
|
29
|
+
let running = 0;
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
31
|
+
const queue = [];
|
|
32
|
+
function dequeue() {
|
|
33
|
+
while (running < maxConcurrent && queue.length > 0) {
|
|
34
|
+
const entry = queue.shift();
|
|
35
|
+
run(entry);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function run(entry) {
|
|
39
|
+
running++;
|
|
40
|
+
entry
|
|
41
|
+
.fn()
|
|
42
|
+
.then((value) => {
|
|
43
|
+
entry.resolve(value);
|
|
44
|
+
}, (error) => {
|
|
45
|
+
entry.reject(error);
|
|
46
|
+
})
|
|
47
|
+
.finally(() => {
|
|
48
|
+
running--;
|
|
49
|
+
dequeue();
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
const handle = {
|
|
53
|
+
get running() {
|
|
54
|
+
return running;
|
|
55
|
+
},
|
|
56
|
+
get queued() {
|
|
57
|
+
return queue.length;
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
function execute(fn) {
|
|
61
|
+
if (running < maxConcurrent) {
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
run({ fn, resolve, reject });
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (queue.length >= maxQueue) {
|
|
67
|
+
return Promise.reject(new types_js_1.BulkheadRejectedError());
|
|
68
|
+
}
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
queue.push({ fn, resolve, reject });
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return { execute, handle };
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=bulkhead.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bulkhead.js","sourceRoot":"","sources":["../../../src/policies/bulkhead.ts"],"names":[],"mappings":";;AA6BA,4BAiEC;AA7FD,0CAAoD;AACpD,0CAAuE;AAEvE,MAAM,QAAQ,GAAG;IACf,aAAa,EAAE,EAAE;IACjB,QAAQ,EAAE,CAAC;CACZ,CAAC;AAQF;;;;;;;;;;;;;GAaG;AACH,SAAgB,QAAQ,CAAC,UAA2B,EAAE;IAIpD,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,QAAQ,CAAC,aAAa,CAAC;IACtE,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ,CAAC;IAEvD,IAAA,gCAAqB,EAAC,aAAa,EAAE,eAAe,CAAC,CAAC;IACtD,IAAA,4BAAiB,EAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IAExC,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,8DAA8D;IAC9D,MAAM,KAAK,GAAsB,EAAE,CAAC;IAEpC,SAAS,OAAO;QACd,OAAO,OAAO,GAAG,aAAa,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACnD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,EAAG,CAAC;YAC7B,GAAG,CAAC,KAAK,CAAC,CAAC;QACb,CAAC;IACH,CAAC;IAED,SAAS,GAAG,CAAI,KAAoB;QAClC,OAAO,EAAE,CAAC;QACV,KAAK;aACF,EAAE,EAAE;aACJ,IAAI,CACH,CAAC,KAAK,EAAE,EAAE;YACR,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC,EACD,CAAC,KAAK,EAAE,EAAE;YACR,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC,CACF;aACA,OAAO,CAAC,GAAG,EAAE;YACZ,OAAO,EAAE,CAAC;YACV,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;IACP,CAAC;IAED,MAAM,MAAM,GAAmB;QAC7B,IAAI,OAAO;YACT,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,IAAI,MAAM;YACR,OAAO,KAAK,CAAC,MAAM,CAAC;QACtB,CAAC;KACF,CAAC;IAEF,SAAS,OAAO,CAAI,EAAoB;QACtC,IAAI,OAAO,GAAG,aAAa,EAAE,CAAC;YAC5B,OAAO,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBACxC,GAAG,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;YAC/B,CAAC,CAAC,CAAC;QACL,CAAC;QAED,IAAI,KAAK,CAAC,MAAM,IAAI,QAAQ,EAAE,CAAC;YAC7B,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,gCAAqB,EAAE,CAAC,CAAC;QACrD,CAAC;QAED,OAAO,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACxC,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;QACtC,CAAC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;AAC7B,CAAC"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cache = cache;
|
|
4
|
+
const utils_js_1 = require("../utils.js");
|
|
5
|
+
const DEFAULTS = {
|
|
6
|
+
ttl: 60_000,
|
|
7
|
+
staleWhileRevalidate: false,
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Creates a TTL-based cache policy that memoizes the result of the wrapped
|
|
11
|
+
* async operation. Supports stale-while-revalidate for background refresh.
|
|
12
|
+
*
|
|
13
|
+
* Returns an object with `execute` (the policy function) and `handle`
|
|
14
|
+
* (an interface to inspect / invalidate the cache).
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* const c = cache({ ttl: 10_000 });
|
|
19
|
+
* const result = await c.execute(() => fetch('/api/data').then(r => r.json()));
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
function cache(options = {}) {
|
|
23
|
+
const ttl = options.ttl ?? DEFAULTS.ttl;
|
|
24
|
+
const staleWhileRevalidate = options.staleWhileRevalidate ?? DEFAULTS.staleWhileRevalidate;
|
|
25
|
+
const keyFn = options.keyFn ?? (() => 'default');
|
|
26
|
+
const onEvict = options.onEvict;
|
|
27
|
+
(0, utils_js_1.assertPositive)(ttl, 'ttl');
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
29
|
+
const store = new Map();
|
|
30
|
+
function evict(key) {
|
|
31
|
+
const entry = store.get(key);
|
|
32
|
+
if (entry) {
|
|
33
|
+
store.delete(key);
|
|
34
|
+
onEvict?.(key, entry.value);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const handle = {
|
|
38
|
+
get size() {
|
|
39
|
+
return store.size;
|
|
40
|
+
},
|
|
41
|
+
invalidate(key) {
|
|
42
|
+
if (key !== undefined) {
|
|
43
|
+
evict(key);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
// Invalidate default key
|
|
47
|
+
evict(keyFn());
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
clear() {
|
|
51
|
+
if (onEvict) {
|
|
52
|
+
for (const [k, v] of store) {
|
|
53
|
+
onEvict(k, v.value);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
store.clear();
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
async function execute(fn) {
|
|
60
|
+
const key = keyFn();
|
|
61
|
+
const now = Date.now();
|
|
62
|
+
const entry = store.get(key);
|
|
63
|
+
if (entry) {
|
|
64
|
+
if (now < entry.expiresAt) {
|
|
65
|
+
// Fresh cache hit
|
|
66
|
+
return entry.value;
|
|
67
|
+
}
|
|
68
|
+
if (staleWhileRevalidate && !entry.stale) {
|
|
69
|
+
// Return stale value and revalidate in background
|
|
70
|
+
entry.stale = true;
|
|
71
|
+
fn()
|
|
72
|
+
.then((value) => {
|
|
73
|
+
store.set(key, {
|
|
74
|
+
value,
|
|
75
|
+
expiresAt: Date.now() + ttl,
|
|
76
|
+
stale: false,
|
|
77
|
+
});
|
|
78
|
+
})
|
|
79
|
+
.catch(() => {
|
|
80
|
+
// Revalidation failed — keep stale value, lift stale flag
|
|
81
|
+
// so next request tries again
|
|
82
|
+
entry.stale = false;
|
|
83
|
+
});
|
|
84
|
+
return entry.value;
|
|
85
|
+
}
|
|
86
|
+
// Expired and not SWR — evict and re-fetch
|
|
87
|
+
evict(key);
|
|
88
|
+
}
|
|
89
|
+
const value = await fn();
|
|
90
|
+
store.set(key, {
|
|
91
|
+
value,
|
|
92
|
+
expiresAt: now + ttl,
|
|
93
|
+
stale: false,
|
|
94
|
+
});
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
return { execute, handle };
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../../src/policies/cache.ts"],"names":[],"mappings":"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"}
|