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
@@ -0,0 +1,13 @@
1
+ // ─── Policies ───────────────────────────────────────────────────────────────
2
+ export { retry } from './policies/retry.js';
3
+ export { circuitBreaker } from './policies/circuit-breaker.js';
4
+ export { timeout } from './policies/timeout.js';
5
+ export { bulkhead } from './policies/bulkhead.js';
6
+ export { fallback } from './policies/fallback.js';
7
+ export { rateLimiter } from './policies/rate-limiter.js';
8
+ export { hedge } from './policies/hedge.js';
9
+ export { cache } from './policies/cache.js';
10
+ // ─── Composition ────────────────────────────────────────────────────────────
11
+ export { pipe, wrap } from './compose.js';
12
+ export { FlowShieldError, TimeoutError, CircuitOpenError, BulkheadRejectedError, RateLimitExceededError, RetryExhaustedError, } from './types.js';
13
+ //# 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,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAC5C,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAE5C,+EAA+E;AAC/E,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAqB1C,OAAO,EACL,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,qBAAqB,EACrB,sBAAsB,EACtB,mBAAmB,GACpB,MAAM,YAAY,CAAC"}
@@ -0,0 +1,72 @@
1
+ import { BulkheadRejectedError } from '../types.js';
2
+ import { assertNonNegative, assertPositiveInteger } from '../utils.js';
3
+ const DEFAULTS = {
4
+ maxConcurrent: 10,
5
+ maxQueue: 0,
6
+ };
7
+ /**
8
+ * Creates a bulkhead (concurrency limiter) policy that limits the number
9
+ * of concurrent executions of the wrapped operation.
10
+ *
11
+ * Returns an object with `execute` (the policy function) and `handle`
12
+ * (an interface to inspect the bulkhead's running / queued counts).
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * const bh = bulkhead({ maxConcurrent: 5, maxQueue: 10 });
17
+ * const result = await bh.execute(() => fetch('/api/data'));
18
+ * console.log(bh.handle.running); // number of active calls
19
+ * ```
20
+ */
21
+ export function bulkhead(options = {}) {
22
+ const maxConcurrent = options.maxConcurrent ?? DEFAULTS.maxConcurrent;
23
+ const maxQueue = options.maxQueue ?? DEFAULTS.maxQueue;
24
+ assertPositiveInteger(maxConcurrent, 'maxConcurrent');
25
+ assertNonNegative(maxQueue, 'maxQueue');
26
+ let running = 0;
27
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
+ const queue = [];
29
+ function dequeue() {
30
+ while (running < maxConcurrent && queue.length > 0) {
31
+ const entry = queue.shift();
32
+ run(entry);
33
+ }
34
+ }
35
+ function run(entry) {
36
+ running++;
37
+ entry
38
+ .fn()
39
+ .then((value) => {
40
+ entry.resolve(value);
41
+ }, (error) => {
42
+ entry.reject(error);
43
+ })
44
+ .finally(() => {
45
+ running--;
46
+ dequeue();
47
+ });
48
+ }
49
+ const handle = {
50
+ get running() {
51
+ return running;
52
+ },
53
+ get queued() {
54
+ return queue.length;
55
+ },
56
+ };
57
+ function execute(fn) {
58
+ if (running < maxConcurrent) {
59
+ return new Promise((resolve, reject) => {
60
+ run({ fn, resolve, reject });
61
+ });
62
+ }
63
+ if (queue.length >= maxQueue) {
64
+ return Promise.reject(new BulkheadRejectedError());
65
+ }
66
+ return new Promise((resolve, reject) => {
67
+ queue.push({ fn, resolve, reject });
68
+ });
69
+ }
70
+ return { execute, handle };
71
+ }
72
+ //# sourceMappingURL=bulkhead.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bulkhead.js","sourceRoot":"","sources":["../../../src/policies/bulkhead.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEvE,MAAM,QAAQ,GAAG;IACf,aAAa,EAAE,EAAE;IACjB,QAAQ,EAAE,CAAC;CACZ,CAAC;AAQF;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,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,qBAAqB,CAAC,aAAa,EAAE,eAAe,CAAC,CAAC;IACtD,iBAAiB,CAAC,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,qBAAqB,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,96 @@
1
+ import { assertPositive } from '../utils.js';
2
+ const DEFAULTS = {
3
+ ttl: 60_000,
4
+ staleWhileRevalidate: false,
5
+ };
6
+ /**
7
+ * Creates a TTL-based cache policy that memoizes the result of the wrapped
8
+ * async operation. Supports stale-while-revalidate for background refresh.
9
+ *
10
+ * Returns an object with `execute` (the policy function) and `handle`
11
+ * (an interface to inspect / invalidate the cache).
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * const c = cache({ ttl: 10_000 });
16
+ * const result = await c.execute(() => fetch('/api/data').then(r => r.json()));
17
+ * ```
18
+ */
19
+ export function cache(options = {}) {
20
+ const ttl = options.ttl ?? DEFAULTS.ttl;
21
+ const staleWhileRevalidate = options.staleWhileRevalidate ?? DEFAULTS.staleWhileRevalidate;
22
+ const keyFn = options.keyFn ?? (() => 'default');
23
+ const onEvict = options.onEvict;
24
+ assertPositive(ttl, 'ttl');
25
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
26
+ const store = new Map();
27
+ function evict(key) {
28
+ const entry = store.get(key);
29
+ if (entry) {
30
+ store.delete(key);
31
+ onEvict?.(key, entry.value);
32
+ }
33
+ }
34
+ const handle = {
35
+ get size() {
36
+ return store.size;
37
+ },
38
+ invalidate(key) {
39
+ if (key !== undefined) {
40
+ evict(key);
41
+ }
42
+ else {
43
+ // Invalidate default key
44
+ evict(keyFn());
45
+ }
46
+ },
47
+ clear() {
48
+ if (onEvict) {
49
+ for (const [k, v] of store) {
50
+ onEvict(k, v.value);
51
+ }
52
+ }
53
+ store.clear();
54
+ },
55
+ };
56
+ async function execute(fn) {
57
+ const key = keyFn();
58
+ const now = Date.now();
59
+ const entry = store.get(key);
60
+ if (entry) {
61
+ if (now < entry.expiresAt) {
62
+ // Fresh cache hit
63
+ return entry.value;
64
+ }
65
+ if (staleWhileRevalidate && !entry.stale) {
66
+ // Return stale value and revalidate in background
67
+ entry.stale = true;
68
+ fn()
69
+ .then((value) => {
70
+ store.set(key, {
71
+ value,
72
+ expiresAt: Date.now() + ttl,
73
+ stale: false,
74
+ });
75
+ })
76
+ .catch(() => {
77
+ // Revalidation failed — keep stale value, lift stale flag
78
+ // so next request tries again
79
+ entry.stale = false;
80
+ });
81
+ return entry.value;
82
+ }
83
+ // Expired and not SWR — evict and re-fetch
84
+ evict(key);
85
+ }
86
+ const value = await fn();
87
+ store.set(key, {
88
+ value,
89
+ expiresAt: now + ttl,
90
+ stale: false,
91
+ });
92
+ return value;
93
+ }
94
+ return { execute, handle };
95
+ }
96
+ //# 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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"}
@@ -0,0 +1,107 @@
1
+ import { CircuitOpenError } from '../types.js';
2
+ import { assertPositiveInteger, assertPositive } from '../utils.js';
3
+ const DEFAULTS = {
4
+ failureThreshold: 5,
5
+ successThreshold: 1,
6
+ resetTimeout: 30_000,
7
+ };
8
+ /**
9
+ * Creates a circuit breaker policy that prevents calls to a failing service.
10
+ *
11
+ * Returns an object with `execute` (the policy function) and `handle`
12
+ * (an interface to inspect / reset the breaker state).
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * const cb = circuitBreaker({ failureThreshold: 3, resetTimeout: 10_000 });
17
+ * const result = await cb.execute(() => fetch('/api/health'));
18
+ * console.log(cb.handle.state); // 'closed'
19
+ * ```
20
+ */
21
+ export function circuitBreaker(options = {}) {
22
+ const failureThreshold = options.failureThreshold ?? DEFAULTS.failureThreshold;
23
+ const successThreshold = options.successThreshold ?? DEFAULTS.successThreshold;
24
+ const resetTimeout = options.resetTimeout ?? DEFAULTS.resetTimeout;
25
+ assertPositiveInteger(failureThreshold, 'failureThreshold');
26
+ assertPositiveInteger(successThreshold, 'successThreshold');
27
+ assertPositive(resetTimeout, 'resetTimeout');
28
+ let state = 'closed';
29
+ let failureCount = 0;
30
+ let successCount = 0;
31
+ let nextAttemptTime = 0;
32
+ function trip() {
33
+ state = 'open';
34
+ nextAttemptTime = Date.now() + resetTimeout;
35
+ options.onOpen?.();
36
+ }
37
+ function resetState() {
38
+ state = 'closed';
39
+ failureCount = 0;
40
+ successCount = 0;
41
+ options.onClose?.();
42
+ }
43
+ function tryTransitionToHalfOpen() {
44
+ if (state === 'open' && Date.now() >= nextAttemptTime) {
45
+ state = 'half-open';
46
+ successCount = 0;
47
+ options.onHalfOpen?.();
48
+ return true;
49
+ }
50
+ return false;
51
+ }
52
+ const handle = {
53
+ get state() {
54
+ // Auto-transition if timer has elapsed
55
+ if (state === 'open' && Date.now() >= nextAttemptTime) {
56
+ state = 'half-open';
57
+ successCount = 0;
58
+ options.onHalfOpen?.();
59
+ }
60
+ return state;
61
+ },
62
+ get failureCount() {
63
+ return failureCount;
64
+ },
65
+ get successCount() {
66
+ return successCount;
67
+ },
68
+ reset() {
69
+ resetState();
70
+ },
71
+ };
72
+ async function execute(fn) {
73
+ if (state === 'open') {
74
+ if (!tryTransitionToHalfOpen()) {
75
+ throw new CircuitOpenError();
76
+ }
77
+ }
78
+ try {
79
+ const result = await fn();
80
+ // Success handling
81
+ if (state === 'half-open') {
82
+ successCount++;
83
+ if (successCount >= successThreshold) {
84
+ resetState();
85
+ }
86
+ }
87
+ else {
88
+ // In closed state, reset failure count on success
89
+ failureCount = 0;
90
+ }
91
+ return result;
92
+ }
93
+ catch (error) {
94
+ failureCount++;
95
+ if (state === 'half-open') {
96
+ // Any failure in half-open trips back to open
97
+ trip();
98
+ }
99
+ else if (failureCount >= failureThreshold) {
100
+ trip();
101
+ }
102
+ throw error;
103
+ }
104
+ }
105
+ return { execute, handle };
106
+ }
107
+ //# sourceMappingURL=circuit-breaker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"circuit-breaker.js","sourceRoot":"","sources":["../../../src/policies/circuit-breaker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Creates a fallback policy that returns a fallback value (or the result
3
+ * of a fallback function) when the wrapped operation fails.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * const result = await fallback({ fallback: [] })(
8
+ * () => fetch('/api/items').then(r => r.json()),
9
+ * );
10
+ * ```
11
+ */
12
+ export function fallback(options) {
13
+ const shouldFallback = options.shouldFallback ?? (() => true);
14
+ return async (fn) => {
15
+ try {
16
+ return await fn();
17
+ }
18
+ catch (error) {
19
+ if (!shouldFallback(error)) {
20
+ throw error;
21
+ }
22
+ const fb = options.fallback;
23
+ if (typeof fb === 'function') {
24
+ return await fb(error);
25
+ }
26
+ return fb;
27
+ }
28
+ };
29
+ }
30
+ //# sourceMappingURL=fallback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fallback.js","sourceRoot":"","sources":["../../../src/policies/fallback.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AACH,MAAM,UAAU,QAAQ,CAAI,OAA2B;IACrD,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IAE9D,OAAO,KAAK,EAAK,EAAoB,EAAkB,EAAE;QACvD,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,EAAE,CAAC;QACpB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC3B,MAAM,KAAK,CAAC;YACd,CAAC;YAED,MAAM,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC;YAC5B,IAAI,OAAO,EAAE,KAAK,UAAU,EAAE,CAAC;gBAC7B,OAAO,MAAO,EAAyC,CAAC,KAAK,CAAC,CAAC;YACjE,CAAC;YACD,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,71 @@
1
+ import { assertPositive } from '../utils.js';
2
+ const DEFAULTS = {
3
+ hedgeDelay: 2000,
4
+ };
5
+ /**
6
+ * Creates a hedge policy that sends a parallel (hedged) request if the
7
+ * primary request doesn't resolve within the configured delay.
8
+ * The first request to resolve wins; the other's result is discarded.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const result = await hedge({ hedgeDelay: 1000 })(
13
+ * () => fetch('/api/data').then(r => r.json()),
14
+ * );
15
+ * ```
16
+ */
17
+ export function hedge(options = {}) {
18
+ const hedgeDelay = options.hedgeDelay ?? DEFAULTS.hedgeDelay;
19
+ assertPositive(hedgeDelay, 'hedgeDelay');
20
+ return async (fn) => {
21
+ return new Promise((resolve, reject) => {
22
+ let settled = false;
23
+ let primaryDone = false;
24
+ let hedgeDone = false;
25
+ let primaryError;
26
+ let hedgeLaunched = false;
27
+ const trySettle = (value) => {
28
+ if (!settled) {
29
+ settled = true;
30
+ clearTimeout(hedgeTimer);
31
+ resolve(value);
32
+ }
33
+ };
34
+ const tryRejectBoth = () => {
35
+ if (primaryDone && hedgeDone && !settled) {
36
+ settled = true;
37
+ reject(primaryError);
38
+ }
39
+ };
40
+ const doLaunchHedge = () => {
41
+ hedgeLaunched = true;
42
+ const hedgeRequest = fn();
43
+ hedgeRequest.then(trySettle, (_hedgeErr) => {
44
+ hedgeDone = true;
45
+ if (!primaryDone && !settled) {
46
+ return;
47
+ }
48
+ tryRejectBoth();
49
+ });
50
+ };
51
+ // Primary request
52
+ const primary = fn();
53
+ primary.then(trySettle, (err) => {
54
+ primaryDone = true;
55
+ primaryError = err;
56
+ if (!hedgeLaunched) {
57
+ clearTimeout(hedgeTimer);
58
+ doLaunchHedge();
59
+ }
60
+ tryRejectBoth();
61
+ });
62
+ // Schedule hedged request
63
+ const hedgeTimer = setTimeout(() => {
64
+ if (!settled) {
65
+ doLaunchHedge();
66
+ }
67
+ }, hedgeDelay);
68
+ });
69
+ };
70
+ }
71
+ //# sourceMappingURL=hedge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hedge.js","sourceRoot":"","sources":["../../../src/policies/hedge.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,QAAQ,GAAG;IACf,UAAU,EAAE,IAAI;CACjB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,KAAK,CAAC,UAAwB,EAAE;IAC9C,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,QAAQ,CAAC,UAAU,CAAC;IAE7D,cAAc,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;IAEzC,OAAO,KAAK,EAAK,EAAoB,EAAc,EAAE;QACnD,OAAO,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACxC,IAAI,OAAO,GAAG,KAAK,CAAC;YACpB,IAAI,WAAW,GAAG,KAAK,CAAC;YACxB,IAAI,SAAS,GAAG,KAAK,CAAC;YACtB,IAAI,YAAqB,CAAC;YAC1B,IAAI,aAAa,GAAG,KAAK,CAAC;YAE1B,MAAM,SAAS,GAAG,CAAC,KAAQ,EAAE,EAAE;gBAC7B,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,OAAO,GAAG,IAAI,CAAC;oBACf,YAAY,CAAC,UAAU,CAAC,CAAC;oBACzB,OAAO,CAAC,KAAK,CAAC,CAAC;gBACjB,CAAC;YACH,CAAC,CAAC;YAEF,MAAM,aAAa,GAAG,GAAG,EAAE;gBACzB,IAAI,WAAW,IAAI,SAAS,IAAI,CAAC,OAAO,EAAE,CAAC;oBACzC,OAAO,GAAG,IAAI,CAAC;oBACf,MAAM,CAAC,YAAY,CAAC,CAAC;gBACvB,CAAC;YACH,CAAC,CAAC;YAEF,MAAM,aAAa,GAAG,GAAG,EAAE;gBACzB,aAAa,GAAG,IAAI,CAAC;gBACrB,MAAM,YAAY,GAAG,EAAE,EAAE,CAAC;gBAC1B,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,SAAkB,EAAE,EAAE;oBAClD,SAAS,GAAG,IAAI,CAAC;oBACjB,IAAI,CAAC,WAAW,IAAI,CAAC,OAAO,EAAE,CAAC;wBAC7B,OAAO;oBACT,CAAC;oBACD,aAAa,EAAE,CAAC;gBAClB,CAAC,CAAC,CAAC;YACL,CAAC,CAAC;YAEF,kBAAkB;YAClB,MAAM,OAAO,GAAG,EAAE,EAAE,CAAC;YACrB,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,GAAY,EAAE,EAAE;gBACvC,WAAW,GAAG,IAAI,CAAC;gBACnB,YAAY,GAAG,GAAG,CAAC;gBAEnB,IAAI,CAAC,aAAa,EAAE,CAAC;oBACnB,YAAY,CAAC,UAAU,CAAC,CAAC;oBACzB,aAAa,EAAE,CAAC;gBAClB,CAAC;gBAED,aAAa,EAAE,CAAC;YAClB,CAAC,CAAC,CAAC;YAEH,0BAA0B;YAC1B,MAAM,UAAU,GAAG,UAAU,CAAC,GAAG,EAAE;gBACjC,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,aAAa,EAAE,CAAC;gBAClB,CAAC;YACH,CAAC,EAAE,UAAU,CAAC,CAAC;QACjB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,89 @@
1
+ import { RateLimitExceededError } from '../types.js';
2
+ import { assertPositiveInteger, assertPositive } from '../utils.js';
3
+ const DEFAULTS = {
4
+ tokensPerInterval: 10,
5
+ interval: 1000,
6
+ rejectOnLimit: false,
7
+ };
8
+ /**
9
+ * Creates a token-bucket rate limiter policy that limits how many times
10
+ * the wrapped operation can be executed within a time interval.
11
+ *
12
+ * Returns an object with `execute` (the policy function) and `handle`
13
+ * (an interface to inspect / reset the bucket).
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * const rl = rateLimiter({ tokensPerInterval: 5, interval: 1000 });
18
+ * const result = await rl.execute(() => fetch('/api/data'));
19
+ * ```
20
+ */
21
+ export function rateLimiter(options = {}) {
22
+ const tokensPerInterval = options.tokensPerInterval ?? DEFAULTS.tokensPerInterval;
23
+ const interval = options.interval ?? DEFAULTS.interval;
24
+ const rejectOnLimit = options.rejectOnLimit ?? DEFAULTS.rejectOnLimit;
25
+ assertPositiveInteger(tokensPerInterval, 'tokensPerInterval');
26
+ assertPositive(interval, 'interval');
27
+ let tokens = tokensPerInterval;
28
+ let lastRefill = Date.now();
29
+ const waiters = [];
30
+ function refill() {
31
+ const now = Date.now();
32
+ const elapsed = now - lastRefill;
33
+ const newTokens = Math.floor((elapsed / interval) * tokensPerInterval);
34
+ if (newTokens > 0) {
35
+ tokens = Math.min(tokensPerInterval, tokens + newTokens);
36
+ lastRefill = now;
37
+ }
38
+ }
39
+ function releaseWaiters() {
40
+ while (tokens > 0 && waiters.length > 0) {
41
+ tokens--;
42
+ const waiter = waiters.shift();
43
+ waiter.resolve();
44
+ }
45
+ }
46
+ // Refill timer — only active when there are waiters
47
+ let refillTimer = null;
48
+ function startRefillTimer() {
49
+ if (refillTimer !== null)
50
+ return;
51
+ refillTimer = setInterval(() => {
52
+ refill();
53
+ releaseWaiters();
54
+ if (waiters.length === 0 && refillTimer !== null) {
55
+ clearInterval(refillTimer);
56
+ refillTimer = null;
57
+ }
58
+ }, Math.min(interval, 100));
59
+ }
60
+ const handle = {
61
+ get availableTokens() {
62
+ refill();
63
+ return tokens;
64
+ },
65
+ reset() {
66
+ tokens = tokensPerInterval;
67
+ lastRefill = Date.now();
68
+ releaseWaiters();
69
+ },
70
+ };
71
+ async function execute(fn) {
72
+ refill();
73
+ if (tokens > 0) {
74
+ tokens--;
75
+ return fn();
76
+ }
77
+ if (rejectOnLimit) {
78
+ throw new RateLimitExceededError();
79
+ }
80
+ // Queue and wait for a token
81
+ await new Promise((resolve) => {
82
+ waiters.push({ resolve });
83
+ startRefillTimer();
84
+ });
85
+ return fn();
86
+ }
87
+ return { execute, handle };
88
+ }
89
+ //# sourceMappingURL=rate-limiter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../../src/policies/rate-limiter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAEpE,MAAM,QAAQ,GAAG;IACf,iBAAiB,EAAE,EAAE;IACrB,QAAQ,EAAE,IAAI;IACd,aAAa,EAAE,KAAK;CACrB,CAAC;AAMF;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,WAAW,CAAC,UAA8B,EAAE;IAI1D,MAAM,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,IAAI,QAAQ,CAAC,iBAAiB,CAAC;IAClF,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ,CAAC;IACvD,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,QAAQ,CAAC,aAAa,CAAC;IAEtE,qBAAqB,CAAC,iBAAiB,EAAE,mBAAmB,CAAC,CAAC;IAC9D,cAAc,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IAErC,IAAI,MAAM,GAAG,iBAAiB,CAAC;IAC/B,IAAI,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC5B,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,SAAS,MAAM;QACb,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,OAAO,GAAG,GAAG,GAAG,UAAU,CAAC;QACjC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,QAAQ,CAAC,GAAG,iBAAiB,CAAC,CAAC;QAEvE,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;YAClB,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,iBAAiB,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;YACzD,UAAU,GAAG,GAAG,CAAC;QACnB,CAAC;IACH,CAAC;IAED,SAAS,cAAc;QACrB,OAAO,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxC,MAAM,EAAE,CAAC;YACT,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,EAAG,CAAC;YAChC,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,CAAC;IACH,CAAC;IAED,oDAAoD;IACpD,IAAI,WAAW,GAA0C,IAAI,CAAC;IAE9D,SAAS,gBAAgB;QACvB,IAAI,WAAW,KAAK,IAAI;YAAE,OAAO;QACjC,WAAW,GAAG,WAAW,CAAC,GAAG,EAAE;YAC7B,MAAM,EAAE,CAAC;YACT,cAAc,EAAE,CAAC;YACjB,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;gBACjD,aAAa,CAAC,WAAW,CAAC,CAAC;gBAC3B,WAAW,GAAG,IAAI,CAAC;YACrB,CAAC;QACH,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;IAC9B,CAAC;IAED,MAAM,MAAM,GAAsB;QAChC,IAAI,eAAe;YACjB,MAAM,EAAE,CAAC;YACT,OAAO,MAAM,CAAC;QAChB,CAAC;QACD,KAAK;YACH,MAAM,GAAG,iBAAiB,CAAC;YAC3B,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACxB,cAAc,EAAE,CAAC;QACnB,CAAC;KACF,CAAC;IAEF,KAAK,UAAU,OAAO,CAAI,EAAoB;QAC5C,MAAM,EAAE,CAAC;QAET,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;YACf,MAAM,EAAE,CAAC;YACT,OAAO,EAAE,EAAE,CAAC;QACd,CAAC;QAED,IAAI,aAAa,EAAE,CAAC;YAClB,MAAM,IAAI,sBAAsB,EAAE,CAAC;QACrC,CAAC;QAED,6BAA6B;QAC7B,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YAClC,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;YAC1B,gBAAgB,EAAE,CAAC;QACrB,CAAC,CAAC,CAAC;QAEH,OAAO,EAAE,EAAE,CAAC;IACd,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,58 @@
1
+ import { RetryExhaustedError } from '../types.js';
2
+ import { sleep, calculateDelay, assertPositiveInteger, assertPositive } from '../utils.js';
3
+ const DEFAULTS = {
4
+ maxAttempts: 3,
5
+ delay: 200,
6
+ backoff: 'exponential',
7
+ maxDelay: 30_000,
8
+ };
9
+ /**
10
+ * Creates a retry policy that re-executes the wrapped operation on failure
11
+ * using the configured backoff strategy.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * const result = await retry({ maxAttempts: 5, backoff: 'exponential' })(
16
+ * () => fetch('/api/data'),
17
+ * );
18
+ * ```
19
+ */
20
+ export function retry(options = {}) {
21
+ const maxAttempts = options.maxAttempts ?? DEFAULTS.maxAttempts;
22
+ const baseDelay = options.delay ?? DEFAULTS.delay;
23
+ const backoff = options.backoff ?? DEFAULTS.backoff;
24
+ const maxDelay = options.maxDelay ?? DEFAULTS.maxDelay;
25
+ const shouldRetry = options.shouldRetry ?? (() => true);
26
+ const onRetry = options.onRetry;
27
+ const signal = options.signal;
28
+ assertPositiveInteger(maxAttempts, 'maxAttempts');
29
+ assertPositive(baseDelay, 'delay');
30
+ assertPositive(maxDelay, 'maxDelay');
31
+ return async (fn) => {
32
+ let lastError;
33
+ let previousDelay = baseDelay;
34
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
35
+ if (signal?.aborted) {
36
+ throw signal.reason;
37
+ }
38
+ try {
39
+ return await fn();
40
+ }
41
+ catch (error) {
42
+ lastError = error;
43
+ if (attempt === maxAttempts) {
44
+ break;
45
+ }
46
+ if (!shouldRetry(error, attempt)) {
47
+ break;
48
+ }
49
+ const delayMs = calculateDelay(attempt, baseDelay, backoff, maxDelay, previousDelay);
50
+ previousDelay = delayMs;
51
+ onRetry?.(error, attempt, delayMs);
52
+ await sleep(delayMs, signal);
53
+ }
54
+ }
55
+ throw new RetryExhaustedError(maxAttempts, lastError);
56
+ };
57
+ }
58
+ //# sourceMappingURL=retry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.js","sourceRoot":"","sources":["../../../src/policies/retry.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE3F,MAAM,QAAQ,GAAG;IACf,WAAW,EAAE,CAAC;IACd,KAAK,EAAE,GAAG;IACV,OAAO,EAAE,aAAsB;IAC/B,QAAQ,EAAE,MAAM;CACjB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,UAAU,KAAK,CAAC,UAAwB,EAAE;IAC9C,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,QAAQ,CAAC,WAAW,CAAC;IAChE,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,IAAI,QAAQ,CAAC,KAAK,CAAC;IAClD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,QAAQ,CAAC,OAAO,CAAC;IACpD,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ,CAAC;IACvD,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IACxD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAChC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAE9B,qBAAqB,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;IAClD,cAAc,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IACnC,cAAc,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IAErC,OAAO,KAAK,EAAK,EAAoB,EAAc,EAAE;QACnD,IAAI,SAAkB,CAAC;QACvB,IAAI,aAAa,GAAG,SAAS,CAAC;QAE9B,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,WAAW,EAAE,OAAO,EAAE,EAAE,CAAC;YACxD,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;gBACpB,MAAM,MAAM,CAAC,MAAM,CAAC;YACtB,CAAC;YAED,IAAI,CAAC;gBACH,OAAO,MAAM,EAAE,EAAE,CAAC;YACpB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,SAAS,GAAG,KAAK,CAAC;gBAElB,IAAI,OAAO,KAAK,WAAW,EAAE,CAAC;oBAC5B,MAAM;gBACR,CAAC;gBAED,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,CAAC;oBACjC,MAAM;gBACR,CAAC;gBAED,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;gBACrF,aAAa,GAAG,OAAO,CAAC;gBAExB,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;gBAEnC,MAAM,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAC/B,CAAC;QACH,CAAC;QAED,MAAM,IAAI,mBAAmB,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;IACxD,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,42 @@
1
+ import { TimeoutError } from '../types.js';
2
+ import { assertPositive } from '../utils.js';
3
+ /**
4
+ * Creates a timeout policy that rejects if the wrapped operation
5
+ * does not resolve within the specified deadline.
6
+ *
7
+ * Uses AbortController for cooperative cancellation when the
8
+ * wrapped function supports it.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const result = await timeout({ ms: 5000 })(() => fetch('/api/data'));
13
+ * ```
14
+ */
15
+ export function timeout(options) {
16
+ assertPositive(options.ms, 'ms');
17
+ return async (fn) => {
18
+ return new Promise((resolve, reject) => {
19
+ let settled = false;
20
+ const timer = setTimeout(() => {
21
+ if (!settled) {
22
+ settled = true;
23
+ reject(new TimeoutError(options.ms));
24
+ }
25
+ }, options.ms);
26
+ fn().then((value) => {
27
+ if (!settled) {
28
+ settled = true;
29
+ clearTimeout(timer);
30
+ resolve(value);
31
+ }
32
+ }, (error) => {
33
+ if (!settled) {
34
+ settled = true;
35
+ clearTimeout(timer);
36
+ reject(error);
37
+ }
38
+ });
39
+ });
40
+ };
41
+ }
42
+ //# sourceMappingURL=timeout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeout.js","sourceRoot":"","sources":["../../../src/policies/timeout.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,OAAO,CAAC,OAAuB;IAC7C,cAAc,CAAC,OAAO,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAEjC,OAAO,KAAK,EAAK,EAAoB,EAAc,EAAE;QACnD,OAAO,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACxC,IAAI,OAAO,GAAG,KAAK,CAAC;YAEpB,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC5B,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,OAAO,GAAG,IAAI,CAAC;oBACf,MAAM,CAAC,IAAI,YAAY,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;gBACvC,CAAC;YACH,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;YAEf,EAAE,EAAE,CAAC,IAAI,CACP,CAAC,KAAK,EAAE,EAAE;gBACR,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,OAAO,GAAG,IAAI,CAAC;oBACf,YAAY,CAAC,KAAK,CAAC,CAAC;oBACpB,OAAO,CAAC,KAAK,CAAC,CAAC;gBACjB,CAAC;YACH,CAAC,EACD,CAAC,KAAK,EAAE,EAAE;gBACR,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,OAAO,GAAG,IAAI,CAAC;oBACf,YAAY,CAAC,KAAK,CAAC,CAAC;oBACpB,MAAM,CAAC,KAAK,CAAC,CAAC;gBAChB,CAAC;YACH,CAAC,CACF,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC"}