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
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.circuitBreaker = circuitBreaker;
|
|
4
|
+
const types_js_1 = require("../types.js");
|
|
5
|
+
const utils_js_1 = require("../utils.js");
|
|
6
|
+
const DEFAULTS = {
|
|
7
|
+
failureThreshold: 5,
|
|
8
|
+
successThreshold: 1,
|
|
9
|
+
resetTimeout: 30_000,
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Creates a circuit breaker policy that prevents calls to a failing service.
|
|
13
|
+
*
|
|
14
|
+
* Returns an object with `execute` (the policy function) and `handle`
|
|
15
|
+
* (an interface to inspect / reset the breaker state).
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* const cb = circuitBreaker({ failureThreshold: 3, resetTimeout: 10_000 });
|
|
20
|
+
* const result = await cb.execute(() => fetch('/api/health'));
|
|
21
|
+
* console.log(cb.handle.state); // 'closed'
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
function circuitBreaker(options = {}) {
|
|
25
|
+
const failureThreshold = options.failureThreshold ?? DEFAULTS.failureThreshold;
|
|
26
|
+
const successThreshold = options.successThreshold ?? DEFAULTS.successThreshold;
|
|
27
|
+
const resetTimeout = options.resetTimeout ?? DEFAULTS.resetTimeout;
|
|
28
|
+
(0, utils_js_1.assertPositiveInteger)(failureThreshold, 'failureThreshold');
|
|
29
|
+
(0, utils_js_1.assertPositiveInteger)(successThreshold, 'successThreshold');
|
|
30
|
+
(0, utils_js_1.assertPositive)(resetTimeout, 'resetTimeout');
|
|
31
|
+
let state = 'closed';
|
|
32
|
+
let failureCount = 0;
|
|
33
|
+
let successCount = 0;
|
|
34
|
+
let nextAttemptTime = 0;
|
|
35
|
+
function trip() {
|
|
36
|
+
state = 'open';
|
|
37
|
+
nextAttemptTime = Date.now() + resetTimeout;
|
|
38
|
+
options.onOpen?.();
|
|
39
|
+
}
|
|
40
|
+
function resetState() {
|
|
41
|
+
state = 'closed';
|
|
42
|
+
failureCount = 0;
|
|
43
|
+
successCount = 0;
|
|
44
|
+
options.onClose?.();
|
|
45
|
+
}
|
|
46
|
+
function tryTransitionToHalfOpen() {
|
|
47
|
+
if (state === 'open' && Date.now() >= nextAttemptTime) {
|
|
48
|
+
state = 'half-open';
|
|
49
|
+
successCount = 0;
|
|
50
|
+
options.onHalfOpen?.();
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
const handle = {
|
|
56
|
+
get state() {
|
|
57
|
+
// Auto-transition if timer has elapsed
|
|
58
|
+
if (state === 'open' && Date.now() >= nextAttemptTime) {
|
|
59
|
+
state = 'half-open';
|
|
60
|
+
successCount = 0;
|
|
61
|
+
options.onHalfOpen?.();
|
|
62
|
+
}
|
|
63
|
+
return state;
|
|
64
|
+
},
|
|
65
|
+
get failureCount() {
|
|
66
|
+
return failureCount;
|
|
67
|
+
},
|
|
68
|
+
get successCount() {
|
|
69
|
+
return successCount;
|
|
70
|
+
},
|
|
71
|
+
reset() {
|
|
72
|
+
resetState();
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
async function execute(fn) {
|
|
76
|
+
if (state === 'open') {
|
|
77
|
+
if (!tryTransitionToHalfOpen()) {
|
|
78
|
+
throw new types_js_1.CircuitOpenError();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const result = await fn();
|
|
83
|
+
// Success handling
|
|
84
|
+
if (state === 'half-open') {
|
|
85
|
+
successCount++;
|
|
86
|
+
if (successCount >= successThreshold) {
|
|
87
|
+
resetState();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// In closed state, reset failure count on success
|
|
92
|
+
failureCount = 0;
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
failureCount++;
|
|
98
|
+
if (state === 'half-open') {
|
|
99
|
+
// Any failure in half-open trips back to open
|
|
100
|
+
trip();
|
|
101
|
+
}
|
|
102
|
+
else if (failureCount >= failureThreshold) {
|
|
103
|
+
trip();
|
|
104
|
+
}
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return { execute, handle };
|
|
109
|
+
}
|
|
110
|
+
//# 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,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fallback = fallback;
|
|
4
|
+
/**
|
|
5
|
+
* Creates a fallback policy that returns a fallback value (or the result
|
|
6
|
+
* of a fallback function) when the wrapped operation fails.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* const result = await fallback({ fallback: [] })(
|
|
11
|
+
* () => fetch('/api/items').then(r => r.json()),
|
|
12
|
+
* );
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
function fallback(options) {
|
|
16
|
+
const shouldFallback = options.shouldFallback ?? (() => true);
|
|
17
|
+
return async (fn) => {
|
|
18
|
+
try {
|
|
19
|
+
return await fn();
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
if (!shouldFallback(error)) {
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
const fb = options.fallback;
|
|
26
|
+
if (typeof fb === 'function') {
|
|
27
|
+
return await fb(error);
|
|
28
|
+
}
|
|
29
|
+
return fb;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=fallback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fallback.js","sourceRoot":"","sources":["../../../src/policies/fallback.ts"],"names":[],"mappings":";;AAaA,4BAkBC;AA7BD;;;;;;;;;;GAUG;AACH,SAAgB,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,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hedge = hedge;
|
|
4
|
+
const utils_js_1 = require("../utils.js");
|
|
5
|
+
const DEFAULTS = {
|
|
6
|
+
hedgeDelay: 2000,
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Creates a hedge policy that sends a parallel (hedged) request if the
|
|
10
|
+
* primary request doesn't resolve within the configured delay.
|
|
11
|
+
* The first request to resolve wins; the other's result is discarded.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* const result = await hedge({ hedgeDelay: 1000 })(
|
|
16
|
+
* () => fetch('/api/data').then(r => r.json()),
|
|
17
|
+
* );
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
function hedge(options = {}) {
|
|
21
|
+
const hedgeDelay = options.hedgeDelay ?? DEFAULTS.hedgeDelay;
|
|
22
|
+
(0, utils_js_1.assertPositive)(hedgeDelay, 'hedgeDelay');
|
|
23
|
+
return async (fn) => {
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
let settled = false;
|
|
26
|
+
let primaryDone = false;
|
|
27
|
+
let hedgeDone = false;
|
|
28
|
+
let primaryError;
|
|
29
|
+
let hedgeLaunched = false;
|
|
30
|
+
const trySettle = (value) => {
|
|
31
|
+
if (!settled) {
|
|
32
|
+
settled = true;
|
|
33
|
+
clearTimeout(hedgeTimer);
|
|
34
|
+
resolve(value);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const tryRejectBoth = () => {
|
|
38
|
+
if (primaryDone && hedgeDone && !settled) {
|
|
39
|
+
settled = true;
|
|
40
|
+
reject(primaryError);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const doLaunchHedge = () => {
|
|
44
|
+
hedgeLaunched = true;
|
|
45
|
+
const hedgeRequest = fn();
|
|
46
|
+
hedgeRequest.then(trySettle, (_hedgeErr) => {
|
|
47
|
+
hedgeDone = true;
|
|
48
|
+
if (!primaryDone && !settled) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
tryRejectBoth();
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
// Primary request
|
|
55
|
+
const primary = fn();
|
|
56
|
+
primary.then(trySettle, (err) => {
|
|
57
|
+
primaryDone = true;
|
|
58
|
+
primaryError = err;
|
|
59
|
+
if (!hedgeLaunched) {
|
|
60
|
+
clearTimeout(hedgeTimer);
|
|
61
|
+
doLaunchHedge();
|
|
62
|
+
}
|
|
63
|
+
tryRejectBoth();
|
|
64
|
+
});
|
|
65
|
+
// Schedule hedged request
|
|
66
|
+
const hedgeTimer = setTimeout(() => {
|
|
67
|
+
if (!settled) {
|
|
68
|
+
doLaunchHedge();
|
|
69
|
+
}
|
|
70
|
+
}, hedgeDelay);
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=hedge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hedge.js","sourceRoot":"","sources":["../../../src/policies/hedge.ts"],"names":[],"mappings":";;AAmBA,sBA8DC;AAhFD,0CAA6C;AAE7C,MAAM,QAAQ,GAAG;IACf,UAAU,EAAE,IAAI;CACjB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,SAAgB,KAAK,CAAC,UAAwB,EAAE;IAC9C,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,QAAQ,CAAC,UAAU,CAAC;IAE7D,IAAA,yBAAc,EAAC,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,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.rateLimiter = rateLimiter;
|
|
4
|
+
const types_js_1 = require("../types.js");
|
|
5
|
+
const utils_js_1 = require("../utils.js");
|
|
6
|
+
const DEFAULTS = {
|
|
7
|
+
tokensPerInterval: 10,
|
|
8
|
+
interval: 1000,
|
|
9
|
+
rejectOnLimit: false,
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Creates a token-bucket rate limiter policy that limits how many times
|
|
13
|
+
* the wrapped operation can be executed within a time interval.
|
|
14
|
+
*
|
|
15
|
+
* Returns an object with `execute` (the policy function) and `handle`
|
|
16
|
+
* (an interface to inspect / reset the bucket).
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* const rl = rateLimiter({ tokensPerInterval: 5, interval: 1000 });
|
|
21
|
+
* const result = await rl.execute(() => fetch('/api/data'));
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
function rateLimiter(options = {}) {
|
|
25
|
+
const tokensPerInterval = options.tokensPerInterval ?? DEFAULTS.tokensPerInterval;
|
|
26
|
+
const interval = options.interval ?? DEFAULTS.interval;
|
|
27
|
+
const rejectOnLimit = options.rejectOnLimit ?? DEFAULTS.rejectOnLimit;
|
|
28
|
+
(0, utils_js_1.assertPositiveInteger)(tokensPerInterval, 'tokensPerInterval');
|
|
29
|
+
(0, utils_js_1.assertPositive)(interval, 'interval');
|
|
30
|
+
let tokens = tokensPerInterval;
|
|
31
|
+
let lastRefill = Date.now();
|
|
32
|
+
const waiters = [];
|
|
33
|
+
function refill() {
|
|
34
|
+
const now = Date.now();
|
|
35
|
+
const elapsed = now - lastRefill;
|
|
36
|
+
const newTokens = Math.floor((elapsed / interval) * tokensPerInterval);
|
|
37
|
+
if (newTokens > 0) {
|
|
38
|
+
tokens = Math.min(tokensPerInterval, tokens + newTokens);
|
|
39
|
+
lastRefill = now;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function releaseWaiters() {
|
|
43
|
+
while (tokens > 0 && waiters.length > 0) {
|
|
44
|
+
tokens--;
|
|
45
|
+
const waiter = waiters.shift();
|
|
46
|
+
waiter.resolve();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Refill timer — only active when there are waiters
|
|
50
|
+
let refillTimer = null;
|
|
51
|
+
function startRefillTimer() {
|
|
52
|
+
if (refillTimer !== null)
|
|
53
|
+
return;
|
|
54
|
+
refillTimer = setInterval(() => {
|
|
55
|
+
refill();
|
|
56
|
+
releaseWaiters();
|
|
57
|
+
if (waiters.length === 0 && refillTimer !== null) {
|
|
58
|
+
clearInterval(refillTimer);
|
|
59
|
+
refillTimer = null;
|
|
60
|
+
}
|
|
61
|
+
}, Math.min(interval, 100));
|
|
62
|
+
}
|
|
63
|
+
const handle = {
|
|
64
|
+
get availableTokens() {
|
|
65
|
+
refill();
|
|
66
|
+
return tokens;
|
|
67
|
+
},
|
|
68
|
+
reset() {
|
|
69
|
+
tokens = tokensPerInterval;
|
|
70
|
+
lastRefill = Date.now();
|
|
71
|
+
releaseWaiters();
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
async function execute(fn) {
|
|
75
|
+
refill();
|
|
76
|
+
if (tokens > 0) {
|
|
77
|
+
tokens--;
|
|
78
|
+
return fn();
|
|
79
|
+
}
|
|
80
|
+
if (rejectOnLimit) {
|
|
81
|
+
throw new types_js_1.RateLimitExceededError();
|
|
82
|
+
}
|
|
83
|
+
// Queue and wait for a token
|
|
84
|
+
await new Promise((resolve) => {
|
|
85
|
+
waiters.push({ resolve });
|
|
86
|
+
startRefillTimer();
|
|
87
|
+
});
|
|
88
|
+
return fn();
|
|
89
|
+
}
|
|
90
|
+
return { execute, handle };
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=rate-limiter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../../src/policies/rate-limiter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.retry = retry;
|
|
4
|
+
const types_js_1 = require("../types.js");
|
|
5
|
+
const utils_js_1 = require("../utils.js");
|
|
6
|
+
const DEFAULTS = {
|
|
7
|
+
maxAttempts: 3,
|
|
8
|
+
delay: 200,
|
|
9
|
+
backoff: 'exponential',
|
|
10
|
+
maxDelay: 30_000,
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Creates a retry policy that re-executes the wrapped operation on failure
|
|
14
|
+
* using the configured backoff strategy.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* const result = await retry({ maxAttempts: 5, backoff: 'exponential' })(
|
|
19
|
+
* () => fetch('/api/data'),
|
|
20
|
+
* );
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
function retry(options = {}) {
|
|
24
|
+
const maxAttempts = options.maxAttempts ?? DEFAULTS.maxAttempts;
|
|
25
|
+
const baseDelay = options.delay ?? DEFAULTS.delay;
|
|
26
|
+
const backoff = options.backoff ?? DEFAULTS.backoff;
|
|
27
|
+
const maxDelay = options.maxDelay ?? DEFAULTS.maxDelay;
|
|
28
|
+
const shouldRetry = options.shouldRetry ?? (() => true);
|
|
29
|
+
const onRetry = options.onRetry;
|
|
30
|
+
const signal = options.signal;
|
|
31
|
+
(0, utils_js_1.assertPositiveInteger)(maxAttempts, 'maxAttempts');
|
|
32
|
+
(0, utils_js_1.assertPositive)(baseDelay, 'delay');
|
|
33
|
+
(0, utils_js_1.assertPositive)(maxDelay, 'maxDelay');
|
|
34
|
+
return async (fn) => {
|
|
35
|
+
let lastError;
|
|
36
|
+
let previousDelay = baseDelay;
|
|
37
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
38
|
+
if (signal?.aborted) {
|
|
39
|
+
throw signal.reason;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
return await fn();
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
lastError = error;
|
|
46
|
+
if (attempt === maxAttempts) {
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
if (!shouldRetry(error, attempt)) {
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
const delayMs = (0, utils_js_1.calculateDelay)(attempt, baseDelay, backoff, maxDelay, previousDelay);
|
|
53
|
+
previousDelay = delayMs;
|
|
54
|
+
onRetry?.(error, attempt, delayMs);
|
|
55
|
+
await (0, utils_js_1.sleep)(delayMs, signal);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
throw new types_js_1.RetryExhaustedError(maxAttempts, lastError);
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=retry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.js","sourceRoot":"","sources":["../../../src/policies/retry.ts"],"names":[],"mappings":";;AAsBA,sBA8CC;AAnED,0CAAkD;AAClD,0CAA2F;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,SAAgB,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,IAAA,gCAAqB,EAAC,WAAW,EAAE,aAAa,CAAC,CAAC;IAClD,IAAA,yBAAc,EAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IACnC,IAAA,yBAAc,EAAC,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,IAAA,yBAAc,EAAC,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,IAAA,gBAAK,EAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAC/B,CAAC;QACH,CAAC;QAED,MAAM,IAAI,8BAAmB,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;IACxD,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.timeout = timeout;
|
|
4
|
+
const types_js_1 = require("../types.js");
|
|
5
|
+
const utils_js_1 = require("../utils.js");
|
|
6
|
+
/**
|
|
7
|
+
* Creates a timeout policy that rejects if the wrapped operation
|
|
8
|
+
* does not resolve within the specified deadline.
|
|
9
|
+
*
|
|
10
|
+
* Uses AbortController for cooperative cancellation when the
|
|
11
|
+
* wrapped function supports it.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* const result = await timeout({ ms: 5000 })(() => fetch('/api/data'));
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
function timeout(options) {
|
|
19
|
+
(0, utils_js_1.assertPositive)(options.ms, 'ms');
|
|
20
|
+
return async (fn) => {
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
let settled = false;
|
|
23
|
+
const timer = setTimeout(() => {
|
|
24
|
+
if (!settled) {
|
|
25
|
+
settled = true;
|
|
26
|
+
reject(new types_js_1.TimeoutError(options.ms));
|
|
27
|
+
}
|
|
28
|
+
}, options.ms);
|
|
29
|
+
fn().then((value) => {
|
|
30
|
+
if (!settled) {
|
|
31
|
+
settled = true;
|
|
32
|
+
clearTimeout(timer);
|
|
33
|
+
resolve(value);
|
|
34
|
+
}
|
|
35
|
+
}, (error) => {
|
|
36
|
+
if (!settled) {
|
|
37
|
+
settled = true;
|
|
38
|
+
clearTimeout(timer);
|
|
39
|
+
reject(error);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=timeout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timeout.js","sourceRoot":"","sources":["../../../src/policies/timeout.ts"],"names":[],"mappings":";;AAgBA,0BAgCC;AA/CD,0CAA2C;AAC3C,0CAA6C;AAE7C;;;;;;;;;;;GAWG;AACH,SAAgB,OAAO,CAAC,OAAuB;IAC7C,IAAA,yBAAc,EAAC,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,uBAAY,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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ─── Core Types ─────────────────────────────────────────────────────────────
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.RetryExhaustedError = exports.RateLimitExceededError = exports.BulkheadRejectedError = exports.CircuitOpenError = exports.TimeoutError = exports.FlowShieldError = void 0;
|
|
5
|
+
// ─── Errors ─────────────────────────────────────────────────────────────────
|
|
6
|
+
class FlowShieldError extends Error {
|
|
7
|
+
constructor(message, cause) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = 'FlowShieldError';
|
|
10
|
+
// Fix prototype chain for instanceof checks
|
|
11
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
12
|
+
if (cause !== undefined) {
|
|
13
|
+
this.cause = cause;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
exports.FlowShieldError = FlowShieldError;
|
|
18
|
+
class TimeoutError extends FlowShieldError {
|
|
19
|
+
constructor(ms) {
|
|
20
|
+
super(`Operation timed out after ${ms}ms`);
|
|
21
|
+
this.name = 'TimeoutError';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.TimeoutError = TimeoutError;
|
|
25
|
+
class CircuitOpenError extends FlowShieldError {
|
|
26
|
+
constructor() {
|
|
27
|
+
super('Circuit breaker is open — request rejected');
|
|
28
|
+
this.name = 'CircuitOpenError';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.CircuitOpenError = CircuitOpenError;
|
|
32
|
+
class BulkheadRejectedError extends FlowShieldError {
|
|
33
|
+
constructor() {
|
|
34
|
+
super('Bulkhead capacity exceeded — request rejected');
|
|
35
|
+
this.name = 'BulkheadRejectedError';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
exports.BulkheadRejectedError = BulkheadRejectedError;
|
|
39
|
+
class RateLimitExceededError extends FlowShieldError {
|
|
40
|
+
constructor() {
|
|
41
|
+
super('Rate limit exceeded — request rejected');
|
|
42
|
+
this.name = 'RateLimitExceededError';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.RateLimitExceededError = RateLimitExceededError;
|
|
46
|
+
class RetryExhaustedError extends FlowShieldError {
|
|
47
|
+
attempts;
|
|
48
|
+
constructor(attempts, lastError) {
|
|
49
|
+
super(`All ${attempts} retry attempts exhausted`, lastError);
|
|
50
|
+
this.name = 'RetryExhaustedError';
|
|
51
|
+
this.attempts = attempts;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
exports.RetryExhaustedError = RetryExhaustedError;
|
|
55
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":";AAAA,+EAA+E;;;AAkJ/E,+EAA+E;AAE/E,MAAa,eAAgB,SAAQ,KAAK;IACxC,YAAY,OAAe,EAAE,KAAe;QAC1C,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;QAC9B,4CAA4C;QAC5C,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAClD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACvB,IAA4B,CAAC,KAAK,GAAG,KAAK,CAAC;QAC9C,CAAC;IACH,CAAC;CACF;AAVD,0CAUC;AAED,MAAa,YAAa,SAAQ,eAAe;IAC/C,YAAY,EAAU;QACpB,KAAK,CAAC,6BAA6B,EAAE,IAAI,CAAC,CAAC;QAC3C,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF;AALD,oCAKC;AAED,MAAa,gBAAiB,SAAQ,eAAe;IACnD;QACE,KAAK,CAAC,4CAA4C,CAAC,CAAC;QACpD,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF;AALD,4CAKC;AAED,MAAa,qBAAsB,SAAQ,eAAe;IACxD;QACE,KAAK,CAAC,+CAA+C,CAAC,CAAC;QACvD,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IACtC,CAAC;CACF;AALD,sDAKC;AAED,MAAa,sBAAuB,SAAQ,eAAe;IACzD;QACE,KAAK,CAAC,wCAAwC,CAAC,CAAC;QAChD,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACvC,CAAC;CACF;AALD,wDAKC;AAED,MAAa,mBAAoB,SAAQ,eAAe;IACtC,QAAQ,CAAS;IACjC,YAAY,QAAgB,EAAE,SAAkB;QAC9C,KAAK,CAAC,OAAO,QAAQ,2BAA2B,EAAE,SAAS,CAAC,CAAC;QAC7D,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;QAClC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;CACF;AAPD,kDAOC"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sleep = sleep;
|
|
4
|
+
exports.calculateDelay = calculateDelay;
|
|
5
|
+
exports.assertPositiveInteger = assertPositiveInteger;
|
|
6
|
+
exports.assertPositive = assertPositive;
|
|
7
|
+
exports.assertNonNegative = assertNonNegative;
|
|
8
|
+
/**
|
|
9
|
+
* Sleep for `ms` milliseconds. Supports cooperative cancellation via AbortSignal.
|
|
10
|
+
*/
|
|
11
|
+
function sleep(ms, signal) {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
if (signal?.aborted) {
|
|
14
|
+
reject(signal.reason);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
let done = false;
|
|
18
|
+
const timer = setTimeout(() => {
|
|
19
|
+
done = true;
|
|
20
|
+
if (signal) {
|
|
21
|
+
signal.removeEventListener('abort', onAbort);
|
|
22
|
+
}
|
|
23
|
+
resolve();
|
|
24
|
+
}, ms);
|
|
25
|
+
const onAbort = () => {
|
|
26
|
+
if (!done) {
|
|
27
|
+
done = true;
|
|
28
|
+
clearTimeout(timer);
|
|
29
|
+
reject(signal.reason);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
if (signal) {
|
|
33
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Calculate the delay for a given attempt using the specified backoff strategy.
|
|
39
|
+
*/
|
|
40
|
+
function calculateDelay(attempt, baseDelay, strategy, maxDelay, previousDelay) {
|
|
41
|
+
let delay;
|
|
42
|
+
switch (strategy) {
|
|
43
|
+
case 'constant':
|
|
44
|
+
delay = baseDelay;
|
|
45
|
+
break;
|
|
46
|
+
case 'linear':
|
|
47
|
+
delay = baseDelay * attempt;
|
|
48
|
+
break;
|
|
49
|
+
case 'exponential':
|
|
50
|
+
delay = baseDelay * Math.pow(2, attempt - 1);
|
|
51
|
+
break;
|
|
52
|
+
case 'decorrelatedJitter': {
|
|
53
|
+
const prev = previousDelay ?? baseDelay;
|
|
54
|
+
delay = Math.random() * (prev * 3 - baseDelay) + baseDelay;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return Math.min(delay, maxDelay);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Validate that a number is a positive integer.
|
|
62
|
+
*/
|
|
63
|
+
function assertPositiveInteger(value, name) {
|
|
64
|
+
if (!Number.isFinite(value) || value < 1 || Math.floor(value) !== value) {
|
|
65
|
+
throw new RangeError(`${name} must be a positive integer, got ${value}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Validate that a number is a positive finite number.
|
|
70
|
+
*/
|
|
71
|
+
function assertPositive(value, name) {
|
|
72
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
73
|
+
throw new RangeError(`${name} must be a positive number, got ${value}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Validate that a number is a non-negative finite number.
|
|
78
|
+
*/
|
|
79
|
+
function assertNonNegative(value, name) {
|
|
80
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
81
|
+
throw new RangeError(`${name} must be a non-negative number, got ${value}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composes multiple policies into a single policy.
|
|
3
|
+
* Policies are applied **left-to-right** (outermost first).
|
|
4
|
+
*
|
|
5
|
+
* `pipe(a, b, c)(fn)` is equivalent to `a(()=> b(()=> c(fn)))`.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const resilient = pipe(
|
|
10
|
+
* timeout({ ms: 5000 }),
|
|
11
|
+
* retry({ maxAttempts: 3 }),
|
|
12
|
+
* );
|
|
13
|
+
* const result = await resilient(() => fetch('/api'));
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export function pipe(...policies) {
|
|
17
|
+
if (policies.length === 0) {
|
|
18
|
+
return (fn) => fn();
|
|
19
|
+
}
|
|
20
|
+
return (fn) => {
|
|
21
|
+
// Build from right to left: the innermost policy wraps fn directly
|
|
22
|
+
let composed = fn;
|
|
23
|
+
for (let i = policies.length - 1; i >= 0; i--) {
|
|
24
|
+
const policy = policies[i];
|
|
25
|
+
const next = composed;
|
|
26
|
+
composed = () => policy(next);
|
|
27
|
+
}
|
|
28
|
+
return composed();
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Wraps an inner policy with an outer policy.
|
|
33
|
+
* `wrap(outer, inner)(fn)` is equivalent to `outer(() => inner(fn))`.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* const policy = wrap(
|
|
38
|
+
* timeout({ ms: 5000 }),
|
|
39
|
+
* retry({ maxAttempts: 3 }),
|
|
40
|
+
* );
|
|
41
|
+
* const result = await policy(() => fetch('/api'));
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export function wrap(outer, inner) {
|
|
45
|
+
return (fn) => {
|
|
46
|
+
return outer(() => inner(fn));
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=compose.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose.js","sourceRoot":"","sources":["../../src/compose.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,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,MAAM,UAAU,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"}
|