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.
Files changed (76) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/LICENSE +21 -0
  3. package/README.md +272 -0
  4. package/dist/cjs/compose.js +53 -0
  5. package/dist/cjs/compose.js.map +1 -0
  6. package/dist/cjs/index.js +32 -0
  7. package/dist/cjs/index.js.map +1 -0
  8. package/dist/cjs/policies/bulkhead.js +75 -0
  9. package/dist/cjs/policies/bulkhead.js.map +1 -0
  10. package/dist/cjs/policies/cache.js +99 -0
  11. package/dist/cjs/policies/cache.js.map +1 -0
  12. package/dist/cjs/policies/circuit-breaker.js +110 -0
  13. package/dist/cjs/policies/circuit-breaker.js.map +1 -0
  14. package/dist/cjs/policies/fallback.js +33 -0
  15. package/dist/cjs/policies/fallback.js.map +1 -0
  16. package/dist/cjs/policies/hedge.js +74 -0
  17. package/dist/cjs/policies/hedge.js.map +1 -0
  18. package/dist/cjs/policies/rate-limiter.js +92 -0
  19. package/dist/cjs/policies/rate-limiter.js.map +1 -0
  20. package/dist/cjs/policies/retry.js +61 -0
  21. package/dist/cjs/policies/retry.js.map +1 -0
  22. package/dist/cjs/policies/timeout.js +45 -0
  23. package/dist/cjs/policies/timeout.js.map +1 -0
  24. package/dist/cjs/types.js +55 -0
  25. package/dist/cjs/types.js.map +1 -0
  26. package/dist/cjs/utils.js +84 -0
  27. package/dist/cjs/utils.js.map +1 -0
  28. package/dist/esm/compose.js +49 -0
  29. package/dist/esm/compose.js.map +1 -0
  30. package/dist/esm/index.js +13 -0
  31. package/dist/esm/index.js.map +1 -0
  32. package/dist/esm/policies/bulkhead.js +72 -0
  33. package/dist/esm/policies/bulkhead.js.map +1 -0
  34. package/dist/esm/policies/cache.js +96 -0
  35. package/dist/esm/policies/cache.js.map +1 -0
  36. package/dist/esm/policies/circuit-breaker.js +107 -0
  37. package/dist/esm/policies/circuit-breaker.js.map +1 -0
  38. package/dist/esm/policies/fallback.js +30 -0
  39. package/dist/esm/policies/fallback.js.map +1 -0
  40. package/dist/esm/policies/hedge.js +71 -0
  41. package/dist/esm/policies/hedge.js.map +1 -0
  42. package/dist/esm/policies/rate-limiter.js +89 -0
  43. package/dist/esm/policies/rate-limiter.js.map +1 -0
  44. package/dist/esm/policies/retry.js +58 -0
  45. package/dist/esm/policies/retry.js.map +1 -0
  46. package/dist/esm/policies/timeout.js +42 -0
  47. package/dist/esm/policies/timeout.js.map +1 -0
  48. package/dist/esm/types.js +46 -0
  49. package/dist/esm/types.js.map +1 -0
  50. package/dist/esm/utils.js +77 -0
  51. package/dist/esm/utils.js.map +1 -0
  52. package/dist/types/compose.d.ts +32 -0
  53. package/dist/types/compose.d.ts.map +1 -0
  54. package/dist/types/index.d.ts +12 -0
  55. package/dist/types/index.d.ts.map +1 -0
  56. package/dist/types/policies/bulkhead.d.ts +20 -0
  57. package/dist/types/policies/bulkhead.d.ts.map +1 -0
  58. package/dist/types/policies/cache.d.ts +19 -0
  59. package/dist/types/policies/cache.d.ts.map +1 -0
  60. package/dist/types/policies/circuit-breaker.d.ts +19 -0
  61. package/dist/types/policies/circuit-breaker.d.ts.map +1 -0
  62. package/dist/types/policies/fallback.d.ts +14 -0
  63. package/dist/types/policies/fallback.d.ts.map +1 -0
  64. package/dist/types/policies/hedge.d.ts +15 -0
  65. package/dist/types/policies/hedge.d.ts.map +1 -0
  66. package/dist/types/policies/rate-limiter.d.ts +19 -0
  67. package/dist/types/policies/rate-limiter.d.ts.map +1 -0
  68. package/dist/types/policies/retry.d.ts +14 -0
  69. package/dist/types/policies/retry.d.ts.map +1 -0
  70. package/dist/types/policies/timeout.d.ts +15 -0
  71. package/dist/types/policies/timeout.d.ts.map +1 -0
  72. package/dist/types/types.d.ts +133 -0
  73. package/dist/types/types.d.ts.map +1 -0
  74. package/dist/types/utils.d.ts +21 -0
  75. package/dist/types/utils.d.ts.map +1 -0
  76. 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
+ [![npm version](https://img.shields.io/npm/v/flowshield.svg)](https://www.npmjs.com/package/flowshield)
6
+ [![npm downloads](https://img.shields.io/npm/dm/flowshield.svg)](https://www.npmjs.com/package/flowshield)
7
+ [![license](https://img.shields.io/npm/l/flowshield.svg)](https://github.com/Avinashvelu03/flowshield/blob/main/LICENSE)
8
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.7+-blue.svg)](https://www.typescriptlang.org/)
9
+ [![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen.svg)](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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"}