@fetchkit/ffetch 3.4.1 → 4.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/README.md +24 -15
- package/dist/chunk-RV6LC73N.js +48 -0
- package/dist/chunk-RV6LC73N.js.map +1 -0
- package/dist/chunk-UP35S5ZH.min.js +2 -0
- package/dist/chunk-UP35S5ZH.min.js.map +1 -0
- package/dist/error-7EEQP46E.min.js +2 -0
- package/dist/error-7EEQP46E.min.js.map +1 -0
- package/dist/error-XBHPSMAQ.js +17 -0
- package/dist/error-XBHPSMAQ.js.map +1 -0
- package/dist/index.cjs +147 -83
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +94 -83
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-

|
|
2
|
+

|
|
3
|
+

|
|
4
4
|
|
|
5
|
-

|
|
6
|
+

|
|
7
7
|
|
|
8
|
-

|
|
9
|
+

|
|
10
|
+

|
|
11
11
|
|
|
12
|
-
# @
|
|
12
|
+
# @fetchkit/ffetch
|
|
13
13
|
|
|
14
14
|
**A production-ready TypeScript-first drop-in replacement for native fetch, or any fetch-compatible implementation.**
|
|
15
15
|
|
|
@@ -25,19 +25,20 @@ ffetch can wrap any fetch-compatible implementation (native fetch, node-fetch, u
|
|
|
25
25
|
- **Per-request overrides** – customize behavior on a per-request basis
|
|
26
26
|
- **Universal** – Node.js, Browser, Cloudflare Workers, React Native
|
|
27
27
|
- **Zero runtime deps** – ships as dual ESM/CJS
|
|
28
|
+
- **Configurable error handling** – custom error types and `throwOnHttpError` flag to throw on HTTP errors
|
|
28
29
|
|
|
29
30
|
## Quick Start
|
|
30
31
|
|
|
31
32
|
### Install
|
|
32
33
|
|
|
33
34
|
```bash
|
|
34
|
-
npm install @
|
|
35
|
+
npm install @fetchkit/ffetch
|
|
35
36
|
```
|
|
36
37
|
|
|
37
38
|
### Basic Usage
|
|
38
39
|
|
|
39
40
|
```typescript
|
|
40
|
-
import createClient from '@
|
|
41
|
+
import createClient from '@fetchkit/ffetch'
|
|
41
42
|
|
|
42
43
|
// Create a client with timeout and retries
|
|
43
44
|
const api = createClient({
|
|
@@ -55,7 +56,7 @@ const data = await response.json()
|
|
|
55
56
|
|
|
56
57
|
```typescript
|
|
57
58
|
// Example: SvelteKit, Next.js, Nuxt, or node-fetch
|
|
58
|
-
import createClient from '@
|
|
59
|
+
import createClient from '@fetchkit/ffetch'
|
|
59
60
|
|
|
60
61
|
// Pass your framework's fetch implementation
|
|
61
62
|
const api = createClient({
|
|
@@ -109,6 +110,12 @@ try {
|
|
|
109
110
|
}
|
|
110
111
|
```
|
|
111
112
|
|
|
113
|
+
### Custom Error Handling with `throwOnHttpError`
|
|
114
|
+
|
|
115
|
+
Native `fetch`'s controversial behavior of not throwing errors for HTTP error status codes (4xx, 5xx) can lead to overlooked errors in applications. By default, `ffetch` follows this same pattern, returning a `Response` object regardless of the HTTP status code. However, with the `throwOnHttpError` flag, developers can configure `ffetch` to throw an `HttpError` for HTTP error responses, making error handling more explicit and robust. Note that this behavior is affected by retries and the circuit breaker - full details are explained in the [Error Handling documentation](./docs/errorhandling.md).
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
|
|
112
119
|
## Documentation
|
|
113
120
|
|
|
114
121
|
| Topic | Description |
|
|
@@ -135,20 +142,22 @@ You can pass any fetch-compatible implementation (native fetch, node-fetch, undi
|
|
|
135
142
|
#### "AbortSignal.any is not a function"
|
|
136
143
|
|
|
137
144
|
```
|
|
145
|
+
|
|
138
146
|
Solution: Install a polyfill for AbortSignal.any
|
|
139
147
|
npm install abort-controller-x
|
|
140
|
-
|
|
148
|
+
|
|
149
|
+
````
|
|
141
150
|
|
|
142
151
|
## CDN Usage
|
|
143
152
|
|
|
144
153
|
```html
|
|
145
154
|
<script type="module">
|
|
146
|
-
import createClient from 'https://unpkg.com/@
|
|
155
|
+
import createClient from 'https://unpkg.com/@fetchkit/ffetch/dist/index.min.js'
|
|
147
156
|
|
|
148
157
|
const api = createClient({ timeout: 5000 })
|
|
149
158
|
const data = await api('/api/data').then((r) => r.json())
|
|
150
159
|
</script>
|
|
151
|
-
|
|
160
|
+
````
|
|
152
161
|
|
|
153
162
|
## Fetch vs. Axios vs. `ffetch`
|
|
154
163
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// src/error.ts
|
|
2
|
+
var BaseError = class extends Error {
|
|
3
|
+
constructor(name, message, cause) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = name;
|
|
6
|
+
if (cause !== void 0) this.cause = cause;
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
var TimeoutError = class extends BaseError {
|
|
10
|
+
constructor(message = "Request timed out", cause) {
|
|
11
|
+
super("TimeoutError", message, cause);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
var CircuitOpenError = class extends BaseError {
|
|
15
|
+
constructor(message = "Circuit is open", cause) {
|
|
16
|
+
super("CircuitOpenError", message, cause);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
var AbortError = class extends BaseError {
|
|
20
|
+
constructor(message = "Request was aborted", cause) {
|
|
21
|
+
super("AbortError", message, cause);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
var RetryLimitError = class extends BaseError {
|
|
25
|
+
constructor(message = "Retry limit reached", cause) {
|
|
26
|
+
super("RetryLimitError", message, cause);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var NetworkError = class extends BaseError {
|
|
30
|
+
constructor(message = "Network error occurred", cause) {
|
|
31
|
+
super("NetworkError", message, cause);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
var HttpError = class extends BaseError {
|
|
35
|
+
constructor(message = "HTTP error occurred", cause) {
|
|
36
|
+
super("HttpError", message, cause);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export {
|
|
41
|
+
TimeoutError,
|
|
42
|
+
CircuitOpenError,
|
|
43
|
+
AbortError,
|
|
44
|
+
RetryLimitError,
|
|
45
|
+
NetworkError,
|
|
46
|
+
HttpError
|
|
47
|
+
};
|
|
48
|
+
//# sourceMappingURL=chunk-RV6LC73N.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/error.ts"],"sourcesContent":["// Base error class to reduce duplication\nclass BaseError extends Error {\n public cause?: unknown\n constructor(name: string, message: string, cause?: unknown) {\n super(message)\n this.name = name\n if (cause !== undefined) this.cause = cause\n }\n}\n\nexport class TimeoutError extends BaseError {\n constructor(message = 'Request timed out', cause?: unknown) {\n super('TimeoutError', message, cause)\n }\n}\n\nexport class CircuitOpenError extends BaseError {\n constructor(message = 'Circuit is open', cause?: unknown) {\n super('CircuitOpenError', message, cause)\n }\n}\n\nexport class AbortError extends BaseError {\n constructor(message = 'Request was aborted', cause?: unknown) {\n super('AbortError', message, cause)\n }\n}\n\nexport class RetryLimitError extends BaseError {\n constructor(message = 'Retry limit reached', cause?: unknown) {\n super('RetryLimitError', message, cause)\n }\n}\n\nexport class NetworkError extends BaseError {\n constructor(message = 'Network error occurred', cause?: unknown) {\n super('NetworkError', message, cause)\n }\n}\n\nexport class HttpError extends BaseError {\n constructor(message = 'HTTP error occurred', cause?: unknown) {\n super('HttpError', message, cause)\n }\n}\n"],"mappings":";AACA,IAAM,YAAN,cAAwB,MAAM;AAAA,EAE5B,YAAY,MAAc,SAAiB,OAAiB;AAC1D,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,QAAI,UAAU,OAAW,MAAK,QAAQ;AAAA,EACxC;AACF;AAEO,IAAM,eAAN,cAA2B,UAAU;AAAA,EAC1C,YAAY,UAAU,qBAAqB,OAAiB;AAC1D,UAAM,gBAAgB,SAAS,KAAK;AAAA,EACtC;AACF;AAEO,IAAM,mBAAN,cAA+B,UAAU;AAAA,EAC9C,YAAY,UAAU,mBAAmB,OAAiB;AACxD,UAAM,oBAAoB,SAAS,KAAK;AAAA,EAC1C;AACF;AAEO,IAAM,aAAN,cAAyB,UAAU;AAAA,EACxC,YAAY,UAAU,uBAAuB,OAAiB;AAC5D,UAAM,cAAc,SAAS,KAAK;AAAA,EACpC;AACF;AAEO,IAAM,kBAAN,cAA8B,UAAU;AAAA,EAC7C,YAAY,UAAU,uBAAuB,OAAiB;AAC5D,UAAM,mBAAmB,SAAS,KAAK;AAAA,EACzC;AACF;AAEO,IAAM,eAAN,cAA2B,UAAU;AAAA,EAC1C,YAAY,UAAU,0BAA0B,OAAiB;AAC/D,UAAM,gBAAgB,SAAS,KAAK;AAAA,EACtC;AACF;AAEO,IAAM,YAAN,cAAwB,UAAU;AAAA,EACvC,YAAY,UAAU,uBAAuB,OAAiB;AAC5D,UAAM,aAAa,SAAS,KAAK;AAAA,EACnC;AACF;","names":[]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var r=class extends Error{constructor(e,n,t){super(n),this.name=e,t!==void 0&&(this.cause=t)}},o=class extends r{constructor(e="Request timed out",n){super("TimeoutError",e,n)}},u=class extends r{constructor(e="Circuit is open",n){super("CircuitOpenError",e,n)}},c=class extends r{constructor(e="Request was aborted",n){super("AbortError",e,n)}},p=class extends r{constructor(e="Retry limit reached",n){super("RetryLimitError",e,n)}},d=class extends r{constructor(e="Network error occurred",n){super("NetworkError",e,n)}},i=class extends r{constructor(e="HTTP error occurred",n){super("HttpError",e,n)}};export{o as a,u as b,c,p as d,d as e,i as f};
|
|
2
|
+
//# sourceMappingURL=chunk-UP35S5ZH.min.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/error.ts"],"sourcesContent":["// Base error class to reduce duplication\nclass BaseError extends Error {\n public cause?: unknown\n constructor(name: string, message: string, cause?: unknown) {\n super(message)\n this.name = name\n if (cause !== undefined) this.cause = cause\n }\n}\n\nexport class TimeoutError extends BaseError {\n constructor(message = 'Request timed out', cause?: unknown) {\n super('TimeoutError', message, cause)\n }\n}\n\nexport class CircuitOpenError extends BaseError {\n constructor(message = 'Circuit is open', cause?: unknown) {\n super('CircuitOpenError', message, cause)\n }\n}\n\nexport class AbortError extends BaseError {\n constructor(message = 'Request was aborted', cause?: unknown) {\n super('AbortError', message, cause)\n }\n}\n\nexport class RetryLimitError extends BaseError {\n constructor(message = 'Retry limit reached', cause?: unknown) {\n super('RetryLimitError', message, cause)\n }\n}\n\nexport class NetworkError extends BaseError {\n constructor(message = 'Network error occurred', cause?: unknown) {\n super('NetworkError', message, cause)\n }\n}\n\nexport class HttpError extends BaseError {\n constructor(message = 'HTTP error occurred', cause?: unknown) {\n super('HttpError', message, cause)\n }\n}\n"],"mappings":"AACA,IAAMA,EAAN,cAAwB,KAAM,CAE5B,YAAYC,EAAcC,EAAiBC,EAAiB,CAC1D,MAAMD,CAAO,EACb,KAAK,KAAOD,EACRE,IAAU,SAAW,KAAK,MAAQA,EACxC,CACF,EAEaC,EAAN,cAA2BJ,CAAU,CAC1C,YAAYE,EAAU,oBAAqBC,EAAiB,CAC1D,MAAM,eAAgBD,EAASC,CAAK,CACtC,CACF,EAEaE,EAAN,cAA+BL,CAAU,CAC9C,YAAYE,EAAU,kBAAmBC,EAAiB,CACxD,MAAM,mBAAoBD,EAASC,CAAK,CAC1C,CACF,EAEaG,EAAN,cAAyBN,CAAU,CACxC,YAAYE,EAAU,sBAAuBC,EAAiB,CAC5D,MAAM,aAAcD,EAASC,CAAK,CACpC,CACF,EAEaI,EAAN,cAA8BP,CAAU,CAC7C,YAAYE,EAAU,sBAAuBC,EAAiB,CAC5D,MAAM,kBAAmBD,EAASC,CAAK,CACzC,CACF,EAEaK,EAAN,cAA2BR,CAAU,CAC1C,YAAYE,EAAU,yBAA0BC,EAAiB,CAC/D,MAAM,eAAgBD,EAASC,CAAK,CACtC,CACF,EAEaM,EAAN,cAAwBT,CAAU,CACvC,YAAYE,EAAU,sBAAuBC,EAAiB,CAC5D,MAAM,YAAaD,EAASC,CAAK,CACnC,CACF","names":["BaseError","name","message","cause","TimeoutError","CircuitOpenError","AbortError","RetryLimitError","NetworkError","HttpError"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AbortError,
|
|
3
|
+
CircuitOpenError,
|
|
4
|
+
HttpError,
|
|
5
|
+
NetworkError,
|
|
6
|
+
RetryLimitError,
|
|
7
|
+
TimeoutError
|
|
8
|
+
} from "./chunk-RV6LC73N.js";
|
|
9
|
+
export {
|
|
10
|
+
AbortError,
|
|
11
|
+
CircuitOpenError,
|
|
12
|
+
HttpError,
|
|
13
|
+
NetworkError,
|
|
14
|
+
RetryLimitError,
|
|
15
|
+
TimeoutError
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=error-XBHPSMAQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/index.cjs
CHANGED
|
@@ -3,6 +3,9 @@ var __defProp = Object.defineProperty;
|
|
|
3
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __esm = (fn, res) => function __init() {
|
|
7
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
8
|
+
};
|
|
6
9
|
var __export = (target, all) => {
|
|
7
10
|
for (var name in all)
|
|
8
11
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -17,6 +20,60 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
20
|
};
|
|
18
21
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
22
|
|
|
23
|
+
// src/error.ts
|
|
24
|
+
var error_exports = {};
|
|
25
|
+
__export(error_exports, {
|
|
26
|
+
AbortError: () => AbortError,
|
|
27
|
+
CircuitOpenError: () => CircuitOpenError,
|
|
28
|
+
HttpError: () => HttpError,
|
|
29
|
+
NetworkError: () => NetworkError,
|
|
30
|
+
RetryLimitError: () => RetryLimitError,
|
|
31
|
+
TimeoutError: () => TimeoutError
|
|
32
|
+
});
|
|
33
|
+
var BaseError, TimeoutError, CircuitOpenError, AbortError, RetryLimitError, NetworkError, HttpError;
|
|
34
|
+
var init_error = __esm({
|
|
35
|
+
"src/error.ts"() {
|
|
36
|
+
"use strict";
|
|
37
|
+
BaseError = class extends Error {
|
|
38
|
+
constructor(name, message, cause) {
|
|
39
|
+
super(message);
|
|
40
|
+
this.name = name;
|
|
41
|
+
if (cause !== void 0) this.cause = cause;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
TimeoutError = class extends BaseError {
|
|
45
|
+
constructor(message = "Request timed out", cause) {
|
|
46
|
+
super("TimeoutError", message, cause);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
CircuitOpenError = class extends BaseError {
|
|
50
|
+
constructor(message = "Circuit is open", cause) {
|
|
51
|
+
super("CircuitOpenError", message, cause);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
AbortError = class extends BaseError {
|
|
55
|
+
constructor(message = "Request was aborted", cause) {
|
|
56
|
+
super("AbortError", message, cause);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
RetryLimitError = class extends BaseError {
|
|
60
|
+
constructor(message = "Retry limit reached", cause) {
|
|
61
|
+
super("RetryLimitError", message, cause);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
NetworkError = class extends BaseError {
|
|
65
|
+
constructor(message = "Network error occurred", cause) {
|
|
66
|
+
super("NetworkError", message, cause);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
HttpError = class extends BaseError {
|
|
70
|
+
constructor(message = "HTTP error occurred", cause) {
|
|
71
|
+
super("HttpError", message, cause);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
20
77
|
// src/index.ts
|
|
21
78
|
var index_exports = {};
|
|
22
79
|
__export(index_exports, {
|
|
@@ -72,41 +129,8 @@ async function retry(fn, retries, delay, shouldRetry2 = () => true, request) {
|
|
|
72
129
|
throw lastErr;
|
|
73
130
|
}
|
|
74
131
|
|
|
75
|
-
// src/error.ts
|
|
76
|
-
var BaseError = class extends Error {
|
|
77
|
-
constructor(name, message, cause) {
|
|
78
|
-
super(message);
|
|
79
|
-
this.name = name;
|
|
80
|
-
if (cause !== void 0) this.cause = cause;
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
var TimeoutError = class extends BaseError {
|
|
84
|
-
constructor(message = "Request timed out", cause) {
|
|
85
|
-
super("TimeoutError", message, cause);
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
var CircuitOpenError = class extends BaseError {
|
|
89
|
-
constructor(message = "Circuit is open", cause) {
|
|
90
|
-
super("CircuitOpenError", message, cause);
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
var AbortError = class extends BaseError {
|
|
94
|
-
constructor(message = "Request was aborted", cause) {
|
|
95
|
-
super("AbortError", message, cause);
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
var RetryLimitError = class extends BaseError {
|
|
99
|
-
constructor(message = "Retry limit reached", cause) {
|
|
100
|
-
super("RetryLimitError", message, cause);
|
|
101
|
-
}
|
|
102
|
-
};
|
|
103
|
-
var NetworkError = class extends BaseError {
|
|
104
|
-
constructor(message = "Network error occurred", cause) {
|
|
105
|
-
super("NetworkError", message, cause);
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
|
|
109
132
|
// src/should-retry.ts
|
|
133
|
+
init_error();
|
|
110
134
|
function shouldRetry(ctx) {
|
|
111
135
|
const { error, response } = ctx;
|
|
112
136
|
if (error instanceof AbortError || error instanceof CircuitOpenError || error instanceof TimeoutError)
|
|
@@ -116,6 +140,7 @@ function shouldRetry(ctx) {
|
|
|
116
140
|
}
|
|
117
141
|
|
|
118
142
|
// src/circuit.ts
|
|
143
|
+
init_error();
|
|
119
144
|
var CircuitBreaker = class {
|
|
120
145
|
constructor(threshold, resetTimeout) {
|
|
121
146
|
this.threshold = threshold;
|
|
@@ -190,6 +215,7 @@ var CircuitBreaker = class {
|
|
|
190
215
|
};
|
|
191
216
|
|
|
192
217
|
// src/client.ts
|
|
218
|
+
init_error();
|
|
193
219
|
function createClient(opts = {}) {
|
|
194
220
|
const {
|
|
195
221
|
timeout: clientDefaultTimeout = 5e3,
|
|
@@ -223,6 +249,7 @@ function createClient(opts = {}) {
|
|
|
223
249
|
request = await effectiveHooks.transformRequest(request);
|
|
224
250
|
}
|
|
225
251
|
await effectiveHooks.before?.(request);
|
|
252
|
+
const effectiveThrowOnHttpError = typeof init.throwOnHttpError !== "undefined" ? init.throwOnHttpError : opts.throwOnHttpError ?? false;
|
|
226
253
|
function createTimeoutSignal(timeout) {
|
|
227
254
|
if (typeof AbortSignal?.timeout === "function") {
|
|
228
255
|
return AbortSignal.timeout(timeout);
|
|
@@ -281,54 +308,33 @@ function createClient(opts = {}) {
|
|
|
281
308
|
}
|
|
282
309
|
return retrying;
|
|
283
310
|
};
|
|
284
|
-
|
|
285
|
-
if (err instanceof DOMException && err.name === "AbortError") {
|
|
286
|
-
if (timeoutSignal?.aborted && (!userSignal || !userSignal.aborted)) {
|
|
287
|
-
return new TimeoutError("signal timed out", err);
|
|
288
|
-
} else {
|
|
289
|
-
return new AbortError("Request was aborted", err);
|
|
290
|
-
}
|
|
291
|
-
} else if (err instanceof TypeError && /NetworkError|network error|failed to fetch|lost connection|NetworkError when attempting to fetch resource/i.test(
|
|
292
|
-
err.message
|
|
293
|
-
)) {
|
|
294
|
-
return new NetworkError(err.message, err);
|
|
295
|
-
}
|
|
296
|
-
return err;
|
|
297
|
-
}
|
|
298
|
-
async function handleError(err) {
|
|
299
|
-
err = mapToCustomError(err);
|
|
300
|
-
if (userSignal?.aborted) {
|
|
301
|
-
const abortErr = new AbortError("Request was aborted by user");
|
|
302
|
-
await effectiveHooks.onAbort?.(request);
|
|
303
|
-
await effectiveHooks.onError?.(request, abortErr);
|
|
304
|
-
await effectiveHooks.onComplete?.(request, void 0, abortErr);
|
|
305
|
-
throw abortErr;
|
|
306
|
-
}
|
|
307
|
-
if (err instanceof TimeoutError || err instanceof NetworkError || err instanceof AbortError) {
|
|
308
|
-
if (err instanceof TimeoutError) {
|
|
309
|
-
await effectiveHooks.onTimeout?.(request);
|
|
310
|
-
}
|
|
311
|
-
if (err instanceof AbortError) {
|
|
312
|
-
await effectiveHooks.onAbort?.(request);
|
|
313
|
-
}
|
|
314
|
-
await effectiveHooks.onError?.(request, err);
|
|
315
|
-
await effectiveHooks.onComplete?.(request, void 0, err);
|
|
316
|
-
throw err;
|
|
317
|
-
}
|
|
318
|
-
const retryErr = new RetryLimitError(
|
|
319
|
-
typeof err === "object" && err && "message" in err && typeof err.message === "string" ? err.message : "Retry limit reached"
|
|
320
|
-
);
|
|
321
|
-
await effectiveHooks.onError?.(request, retryErr);
|
|
322
|
-
await effectiveHooks.onComplete?.(request, void 0, retryErr);
|
|
323
|
-
throw retryErr;
|
|
324
|
-
}
|
|
311
|
+
let lastResponse = void 0;
|
|
325
312
|
try {
|
|
326
313
|
let res = await retry(
|
|
327
314
|
async () => {
|
|
315
|
+
if (userSignal?.aborted) {
|
|
316
|
+
effectiveHooks.onAbort?.(request);
|
|
317
|
+
throw new AbortError("Request was aborted by user");
|
|
318
|
+
}
|
|
319
|
+
if (timeoutSignal?.aborted) {
|
|
320
|
+
effectiveHooks.onTimeout?.(request);
|
|
321
|
+
throw new TimeoutError("signal timed out");
|
|
322
|
+
}
|
|
328
323
|
if (typeof combinedSignal?.throwIfAborted === "function") {
|
|
329
324
|
combinedSignal.throwIfAborted();
|
|
330
325
|
} else if (combinedSignal?.aborted) {
|
|
331
|
-
|
|
326
|
+
if (userSignal?.aborted) {
|
|
327
|
+
effectiveHooks.onAbort?.(request);
|
|
328
|
+
throw new AbortError("Request was aborted by user");
|
|
329
|
+
} else if (timeoutSignal?.aborted) {
|
|
330
|
+
effectiveHooks.onTimeout?.(request);
|
|
331
|
+
throw new TimeoutError("signal timed out");
|
|
332
|
+
} else {
|
|
333
|
+
throw new AbortError(
|
|
334
|
+
"Request was aborted",
|
|
335
|
+
new DOMException("Aborted", "AbortError")
|
|
336
|
+
);
|
|
337
|
+
}
|
|
332
338
|
}
|
|
333
339
|
const reqWithSignal = new Request(request, {
|
|
334
340
|
signal: combinedSignal
|
|
@@ -336,15 +342,32 @@ function createClient(opts = {}) {
|
|
|
336
342
|
try {
|
|
337
343
|
const handler = fetchHandler ?? fetch;
|
|
338
344
|
const response = await handler(reqWithSignal);
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
}
|
|
345
|
+
lastResponse = response;
|
|
346
|
+
if (breaker && (response.status >= 500 || response.status === 429)) {
|
|
347
|
+
breaker.recordResult(response, void 0, request);
|
|
343
348
|
}
|
|
344
349
|
return response;
|
|
345
350
|
} catch (err) {
|
|
346
351
|
if (breaker) breaker.recordResult(void 0, err, request);
|
|
347
|
-
|
|
352
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
353
|
+
if (timeoutSignal?.aborted && (!userSignal || !userSignal.aborted)) {
|
|
354
|
+
effectiveHooks.onTimeout?.(request);
|
|
355
|
+
throw new TimeoutError("signal timed out", err);
|
|
356
|
+
} else if (userSignal?.aborted) {
|
|
357
|
+
effectiveHooks.onAbort?.(request);
|
|
358
|
+
throw new AbortError("Request was aborted by user");
|
|
359
|
+
} else {
|
|
360
|
+
throw new AbortError(
|
|
361
|
+
"Request was aborted",
|
|
362
|
+
new DOMException("Aborted", "AbortError")
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
} else if (err instanceof TypeError && /NetworkError|network error|failed to fetch|lost connection|NetworkError when attempting to fetch resource/i.test(
|
|
366
|
+
err.message
|
|
367
|
+
)) {
|
|
368
|
+
throw new NetworkError(err.message, err);
|
|
369
|
+
}
|
|
370
|
+
throw err;
|
|
348
371
|
}
|
|
349
372
|
},
|
|
350
373
|
effectiveRetries,
|
|
@@ -357,10 +380,50 @@ function createClient(opts = {}) {
|
|
|
357
380
|
}
|
|
358
381
|
await effectiveHooks.after?.(request, res);
|
|
359
382
|
await effectiveHooks.onComplete?.(request, res, void 0);
|
|
383
|
+
if (effectiveThrowOnHttpError && (res.status >= 400 && res.status < 500 && res.status !== 429 || res.status >= 500 || res.status === 429)) {
|
|
384
|
+
const { HttpError: HttpError2 } = await Promise.resolve().then(() => (init_error(), error_exports));
|
|
385
|
+
throw new HttpError2(
|
|
386
|
+
`HTTP error: ${res.status} ${res.statusText}`,
|
|
387
|
+
res
|
|
388
|
+
);
|
|
389
|
+
}
|
|
360
390
|
return res;
|
|
361
391
|
} catch (err) {
|
|
362
|
-
|
|
363
|
-
|
|
392
|
+
if (lastResponse) {
|
|
393
|
+
const resp = lastResponse;
|
|
394
|
+
if (effectiveThrowOnHttpError && (resp.status >= 400 && resp.status < 500 && resp.status !== 429 || resp.status >= 500 || resp.status === 429)) {
|
|
395
|
+
const { HttpError: HttpError2 } = await Promise.resolve().then(() => (init_error(), error_exports));
|
|
396
|
+
throw new HttpError2(
|
|
397
|
+
`HTTP error: ${resp.status} ${resp.statusText}`,
|
|
398
|
+
resp
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
return resp;
|
|
402
|
+
}
|
|
403
|
+
if (err instanceof TimeoutError) {
|
|
404
|
+
await effectiveHooks.onTimeout?.(request);
|
|
405
|
+
await effectiveHooks.onError?.(request, err);
|
|
406
|
+
await effectiveHooks.onComplete?.(request, void 0, err);
|
|
407
|
+
throw err;
|
|
408
|
+
}
|
|
409
|
+
if (err instanceof AbortError) {
|
|
410
|
+
await effectiveHooks.onAbort?.(request);
|
|
411
|
+
await effectiveHooks.onError?.(request, err);
|
|
412
|
+
await effectiveHooks.onComplete?.(request, void 0, err);
|
|
413
|
+
throw err;
|
|
414
|
+
}
|
|
415
|
+
if (err instanceof NetworkError) {
|
|
416
|
+
await effectiveHooks.onError?.(request, err);
|
|
417
|
+
await effectiveHooks.onComplete?.(request, void 0, err);
|
|
418
|
+
throw err;
|
|
419
|
+
}
|
|
420
|
+
const retryErr = new RetryLimitError(
|
|
421
|
+
typeof err === "object" && err && "message" in err && typeof err.message === "string" ? err.message : "Retry limit reached",
|
|
422
|
+
err
|
|
423
|
+
);
|
|
424
|
+
await effectiveHooks.onError?.(request, retryErr);
|
|
425
|
+
await effectiveHooks.onComplete?.(request, void 0, retryErr);
|
|
426
|
+
throw retryErr;
|
|
364
427
|
}
|
|
365
428
|
};
|
|
366
429
|
const promise = breaker ? breaker.invoke(retryWithHooks).catch(async (err) => {
|
|
@@ -410,6 +473,7 @@ function createClient(opts = {}) {
|
|
|
410
473
|
}
|
|
411
474
|
|
|
412
475
|
// src/index.ts
|
|
476
|
+
init_error();
|
|
413
477
|
var index_default = createClient;
|
|
414
478
|
// Annotate the CommonJS export names for ESM import in node:
|
|
415
479
|
0 && (module.exports = {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/retry.ts","../src/error.ts","../src/should-retry.ts","../src/circuit.ts","../src/client.ts"],"sourcesContent":["export type { FFetch, FFetchOptions } from './types'\nexport type { Hooks } from './hooks'\n\nimport { createClient } from './client'\nexport { createClient } from './client'\n\nexport {\n TimeoutError,\n CircuitOpenError,\n AbortError,\n RetryLimitError,\n NetworkError,\n} from './error'\n\nexport default createClient\n","import type { RetryContext } from './types.js'\n\nexport type RetryDelay = number | ((ctx: RetryContext) => number)\n\nexport const defaultDelay: RetryDelay = (ctx) => {\n const retryAfter = ctx.response?.headers.get('Retry-After')\n if (retryAfter) {\n const seconds = parseInt(retryAfter, 10)\n if (!isNaN(seconds)) return seconds * 1000\n const date = Date.parse(retryAfter)\n if (!isNaN(date)) return Math.max(0, date - Date.now())\n }\n return 2 ** ctx.attempt * 200 + Math.random() * 100\n}\n\nexport async function retry(\n fn: () => Promise<Response>,\n retries: number,\n delay: RetryDelay,\n shouldRetry: (ctx: RetryContext) => boolean = () => true,\n request: Request\n): Promise<Response> {\n let lastErr: unknown\n let lastRes: Response | undefined\n\n for (let i = 0; i <= retries; i++) {\n const ctx: RetryContext = {\n attempt: i + 1,\n request,\n response: lastRes,\n error: lastErr,\n }\n try {\n lastRes = await fn()\n ctx.response = lastRes\n ctx.error = undefined\n if (i < retries && shouldRetry(ctx)) {\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await new Promise((r) => setTimeout(r, wait))\n continue\n }\n return lastRes\n } catch (err) {\n lastErr = err\n ctx.error = err\n if (i === retries || !shouldRetry(ctx)) throw err\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await new Promise((r) => setTimeout(r, wait))\n }\n }\n throw lastErr\n}\n","// Base error class to reduce duplication\nclass BaseError extends Error {\n public cause?: unknown\n constructor(name: string, message: string, cause?: unknown) {\n super(message)\n this.name = name\n if (cause !== undefined) this.cause = cause\n }\n}\n\nexport class TimeoutError extends BaseError {\n constructor(message = 'Request timed out', cause?: unknown) {\n super('TimeoutError', message, cause)\n }\n}\n\nexport class CircuitOpenError extends BaseError {\n constructor(message = 'Circuit is open', cause?: unknown) {\n super('CircuitOpenError', message, cause)\n }\n}\n\nexport class AbortError extends BaseError {\n constructor(message = 'Request was aborted', cause?: unknown) {\n super('AbortError', message, cause)\n }\n}\n\nexport class RetryLimitError extends BaseError {\n constructor(message = 'Retry limit reached', cause?: unknown) {\n super('RetryLimitError', message, cause)\n }\n}\n\nexport class NetworkError extends BaseError {\n constructor(message = 'Network error occurred', cause?: unknown) {\n super('NetworkError', message, cause)\n }\n}\n","import { AbortError, CircuitOpenError, TimeoutError } from './error.js'\nimport type { RetryContext } from './types.js'\n\nexport function shouldRetry(ctx: RetryContext): boolean {\n const { error, response } = ctx\n if (\n error instanceof AbortError ||\n error instanceof CircuitOpenError ||\n error instanceof TimeoutError\n )\n return false\n if (!response) return true // network error\n return response.status >= 500 || response.status === 429\n}\n","import { CircuitOpenError, RetryLimitError } from './error.js'\n\nexport class CircuitBreaker {\n private failures = 0\n private nextAttempt = 0\n private isOpen = false\n\n // Returns true if the circuit breaker is currently open (blocking requests).\n get open(): boolean {\n return this.isOpen\n }\n private hooks?: {\n onCircuitOpen?: (req: Request) => void | Promise<void>\n onCircuitClose?: (req: Request) => void | Promise<void>\n }\n private lastSuccessRequest?: Request\n private lastOpenRequest?: Request\n\n constructor(\n private threshold: number,\n private resetTimeout: number\n ) {}\n\n // Call this after each request to record the result and update circuit state.\n // Returns true if the result was counted as a failure.\n recordResult(response?: Response, error?: unknown, req?: Request): boolean {\n // Count thrown errors (network, abort, timeout) as failures\n if (error && !(error instanceof RetryLimitError)) {\n this.setLastOpenRequest(req!)\n this.onFailure()\n return true\n }\n // Count HTTP 5xx and 429 responses as failures\n if (response && (response.status >= 500 || response.status === 429)) {\n this.setLastOpenRequest(req!)\n this.onFailure()\n return true\n }\n // Otherwise, count as success\n if (req) this.setLastSuccessRequest(req)\n this.onSuccess()\n return false\n }\n\n setHooks(hooks: {\n onCircuitOpen?: (req: Request) => void | Promise<void>\n onCircuitClose?: (req: Request) => void | Promise<void>\n }) {\n this.hooks = hooks\n }\n setLastOpenRequest(req: Request) {\n this.lastOpenRequest = req\n }\n\n setLastSuccessRequest(req: Request) {\n this.lastSuccessRequest = req\n }\n\n async invoke<T>(fn: () => Promise<T>): Promise<T> {\n if (Date.now() < this.nextAttempt)\n throw new CircuitOpenError('Circuit is open')\n try {\n const res = await fn()\n this.onSuccess()\n return res\n } catch (err) {\n this.onFailure()\n throw err\n }\n }\n\n private onSuccess() {\n const wasOpen = this.isOpen\n this.failures = 0\n if (wasOpen) {\n this.isOpen = false\n if (this.hooks?.onCircuitClose && this.lastSuccessRequest) {\n this.hooks.onCircuitClose(this.lastSuccessRequest)\n }\n }\n this.lastSuccessRequest = undefined\n }\n\n private onFailure() {\n this.failures++\n if (this.failures >= this.threshold) {\n this.nextAttempt = Date.now() + this.resetTimeout\n this.isOpen = true\n if (this.hooks?.onCircuitOpen && this.lastOpenRequest) {\n this.hooks.onCircuitOpen(this.lastOpenRequest)\n }\n }\n }\n}\n","import type {\n FFetchOptions,\n FFetch,\n FFetchRequestInit,\n PendingRequest,\n} from './types.js'\nimport { retry, defaultDelay } from './retry.js'\nimport { shouldRetry as defaultShouldRetry } from './should-retry.js'\nimport { CircuitBreaker } from './circuit.js'\nimport {\n TimeoutError,\n CircuitOpenError,\n AbortError,\n RetryLimitError,\n NetworkError,\n} from './error.js'\n\nexport function createClient(opts: FFetchOptions = {}): FFetch {\n const {\n timeout: clientDefaultTimeout = 5_000,\n retries: clientDefaultRetries = 0,\n retryDelay: clientDefaultRetryDelay = defaultDelay,\n shouldRetry: clientDefaultShouldRetry = defaultShouldRetry,\n hooks: clientDefaultHooks = {},\n circuit: clientDefaultCircuit,\n fetchHandler,\n } = opts\n\n const breaker = clientDefaultCircuit\n ? new CircuitBreaker(\n clientDefaultCircuit.threshold,\n clientDefaultCircuit.reset\n )\n : null\n\n if (\n breaker &&\n (clientDefaultHooks.onCircuitClose || clientDefaultHooks.onCircuitOpen)\n ) {\n breaker.setHooks({\n onCircuitClose: clientDefaultHooks.onCircuitClose,\n onCircuitOpen: clientDefaultHooks.onCircuitOpen,\n })\n }\n\n const pendingRequests: PendingRequest[] = []\n\n // Helper to abort all pending requests\n function abortAll() {\n for (const entry of pendingRequests) {\n entry.controller?.abort()\n }\n }\n\n const client = async (\n input: RequestInfo | URL,\n init: FFetchRequestInit = {}\n ) => {\n // No longer require AbortSignal.timeout - we'll implement it manually if needed\n let request = new Request(input, init)\n\n // Merge hooks: per-request hooks override client hooks, but fallback to client hooks\n const effectiveHooks = { ...clientDefaultHooks, ...(init.hooks || {}) }\n if (effectiveHooks.transformRequest) {\n request = await effectiveHooks.transformRequest(request)\n }\n await effectiveHooks.before?.(request)\n\n // Create timeout signal (manual implementation if AbortSignal.timeout not available)\n function createTimeoutSignal(timeout: number): AbortSignal {\n if (typeof AbortSignal?.timeout === 'function') {\n return AbortSignal.timeout(timeout)\n }\n\n // Manual implementation for older environments\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n\n // Clean up timeout if signal is aborted early by other means\n controller.signal.addEventListener(\n 'abort',\n () => clearTimeout(timeoutId),\n { once: true }\n )\n\n return controller.signal\n }\n\n // AbortSignal.timeout/any logic ---\n const effectiveTimeout = init.timeout ?? clientDefaultTimeout\n const userSignal = init.signal\n const transformedSignal = request.signal // Extract signal from transformed request\n let timeoutSignal: AbortSignal | undefined = undefined\n let combinedSignal: AbortSignal | undefined = undefined\n let controller: AbortController | undefined = undefined\n\n if (effectiveTimeout > 0) {\n timeoutSignal = createTimeoutSignal(effectiveTimeout)\n }\n\n // Collect all signals that need to be combined\n const signals: AbortSignal[] = []\n if (userSignal) signals.push(userSignal)\n if (transformedSignal && transformedSignal !== userSignal) {\n signals.push(transformedSignal)\n }\n if (timeoutSignal) signals.push(timeoutSignal)\n\n // Use AbortSignal.any for signal combination. Requires native support or a polyfill.\n // If not available, instruct users to install a polyfill for environments lacking AbortSignal.any.\n // there are always 1 or more signals\n if (signals.length === 1) {\n combinedSignal = signals[0]\n controller = new AbortController()\n } else {\n if (typeof AbortSignal.any !== 'function') {\n throw new Error(\n 'AbortSignal.any is required for combining multiple signals. Please install a polyfill for environments that do not support it.'\n )\n }\n combinedSignal = AbortSignal.any(signals)\n controller = new AbortController()\n }\n const retryWithHooks = async () => {\n const effectiveRetries = init.retries ?? clientDefaultRetries\n const effectiveRetryDelay =\n typeof init.retryDelay !== 'undefined'\n ? init.retryDelay\n : clientDefaultRetryDelay\n const effectiveShouldRetry = init.shouldRetry ?? clientDefaultShouldRetry\n\n // Wrap shouldRetry to call onRetry hook\n let attempt = 0\n const shouldRetryWithHook = (ctx: import('./types').RetryContext) => {\n attempt = ctx.attempt\n const retrying = effectiveShouldRetry(ctx)\n if (retrying && attempt <= effectiveRetries) {\n effectiveHooks.onRetry?.(\n request,\n attempt - 1,\n ctx.error,\n ctx.response\n )\n }\n return retrying\n }\n\n function mapToCustomError(err: unknown): unknown {\n if (err instanceof DOMException && err.name === 'AbortError') {\n if (timeoutSignal?.aborted && (!userSignal || !userSignal.aborted)) {\n return new TimeoutError('signal timed out', err)\n } else {\n return new AbortError('Request was aborted', err)\n }\n } else if (\n err instanceof TypeError &&\n /NetworkError|network error|failed to fetch|lost connection|NetworkError when attempting to fetch resource/i.test(\n err.message\n )\n ) {\n return new NetworkError(err.message, err)\n }\n return err\n }\n\n async function handleError(err: unknown): Promise<never> {\n err = mapToCustomError(err)\n // If user aborted, always throw AbortError, not RetryLimitError\n if (userSignal?.aborted) {\n const abortErr = new AbortError('Request was aborted by user')\n await effectiveHooks.onAbort?.(request)\n await effectiveHooks.onError?.(request, abortErr)\n await effectiveHooks.onComplete?.(request, undefined, abortErr)\n throw abortErr\n }\n // If the error is a custom error, re-throw it directly (do not wrap)\n if (\n err instanceof TimeoutError ||\n err instanceof NetworkError ||\n err instanceof AbortError\n ) {\n if (err instanceof TimeoutError) {\n await effectiveHooks.onTimeout?.(request)\n }\n if (err instanceof AbortError) {\n await effectiveHooks.onAbort?.(request)\n }\n await effectiveHooks.onError?.(request, err)\n await effectiveHooks.onComplete?.(request, undefined, err)\n throw err\n }\n // Otherwise, throw RetryLimitError after all retries are exhausted\n const retryErr = new RetryLimitError(\n typeof err === 'object' &&\n err &&\n 'message' in err &&\n typeof (err as { message?: unknown }).message === 'string'\n ? (err as { message: string }).message\n : 'Retry limit reached'\n )\n await effectiveHooks.onError?.(request, retryErr)\n await effectiveHooks.onComplete?.(request, undefined, retryErr)\n throw retryErr\n }\n\n try {\n let res = await retry(\n async () => {\n // Use AbortSignal.throwIfAborted() before starting fetch\n if (typeof combinedSignal?.throwIfAborted === 'function') {\n combinedSignal.throwIfAborted()\n } else if (combinedSignal?.aborted) {\n throw new AbortError('Request was aborted')\n }\n const reqWithSignal = new Request(request, {\n signal: combinedSignal,\n })\n try {\n const handler = fetchHandler ?? fetch\n const response = await handler(reqWithSignal)\n // Circuit breaker: record result\n if (breaker) {\n if (breaker.recordResult(response, undefined, request)) {\n throw new Error(`HTTP error: ${response.status}`)\n }\n }\n return response\n } catch (err) {\n if (breaker) breaker.recordResult(undefined, err, request)\n throw mapToCustomError(err)\n }\n },\n effectiveRetries,\n effectiveRetryDelay,\n shouldRetryWithHook,\n request\n )\n if (effectiveHooks.transformResponse) {\n res = await effectiveHooks.transformResponse(res, request)\n }\n await effectiveHooks.after?.(request, res)\n await effectiveHooks.onComplete?.(request, res, undefined)\n return res\n } catch (err: unknown) {\n await handleError(err)\n throw new Error('Unreachable: handleError should always throw')\n }\n }\n\n const promise = breaker\n ? breaker.invoke(retryWithHooks).catch(async (err: unknown) => {\n if (err instanceof CircuitOpenError) {\n await effectiveHooks.onCircuitOpen?.(request)\n await effectiveHooks.onError?.(request, err)\n await effectiveHooks.onComplete?.(request, undefined, err)\n } else {\n await effectiveHooks.onError?.(request, err)\n await effectiveHooks.onComplete?.(request, undefined, err)\n }\n throw err\n })\n : retryWithHooks()\n\n const entry: PendingRequest = {\n promise,\n request,\n controller,\n }\n pendingRequests.push(entry)\n\n return promise.finally(() => {\n const index = pendingRequests.indexOf(entry)\n if (index > -1) {\n pendingRequests.splice(index, 1)\n }\n })\n }\n\n // Add pendingRequests property to the client function (read-only)\n Object.defineProperty(client, 'pendingRequests', {\n get() {\n return pendingRequests\n },\n enumerable: false,\n configurable: false,\n })\n\n // Add abortAll method to the client function (read-only)\n Object.defineProperty(client, 'abortAll', {\n value: abortAll,\n writable: false,\n enumerable: false,\n configurable: false,\n })\n\n // Expose circuit breaker open state\n Object.defineProperty(client, 'circuitOpen', {\n get() {\n return breaker ? breaker.open : false\n },\n enumerable: true,\n })\n\n return client as FFetch\n}\n"],"mappings":"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","names":["shouldRetry","controller"]}
|
|
1
|
+
{"version":3,"sources":["../src/error.ts","../src/index.ts","../src/retry.ts","../src/should-retry.ts","../src/circuit.ts","../src/client.ts"],"sourcesContent":["// Base error class to reduce duplication\nclass BaseError extends Error {\n public cause?: unknown\n constructor(name: string, message: string, cause?: unknown) {\n super(message)\n this.name = name\n if (cause !== undefined) this.cause = cause\n }\n}\n\nexport class TimeoutError extends BaseError {\n constructor(message = 'Request timed out', cause?: unknown) {\n super('TimeoutError', message, cause)\n }\n}\n\nexport class CircuitOpenError extends BaseError {\n constructor(message = 'Circuit is open', cause?: unknown) {\n super('CircuitOpenError', message, cause)\n }\n}\n\nexport class AbortError extends BaseError {\n constructor(message = 'Request was aborted', cause?: unknown) {\n super('AbortError', message, cause)\n }\n}\n\nexport class RetryLimitError extends BaseError {\n constructor(message = 'Retry limit reached', cause?: unknown) {\n super('RetryLimitError', message, cause)\n }\n}\n\nexport class NetworkError extends BaseError {\n constructor(message = 'Network error occurred', cause?: unknown) {\n super('NetworkError', message, cause)\n }\n}\n\nexport class HttpError extends BaseError {\n constructor(message = 'HTTP error occurred', cause?: unknown) {\n super('HttpError', message, cause)\n }\n}\n","export type { FFetch, FFetchOptions } from './types'\nexport type { Hooks } from './hooks'\n\nimport { createClient } from './client'\nexport { createClient } from './client'\n\nexport {\n TimeoutError,\n CircuitOpenError,\n AbortError,\n RetryLimitError,\n NetworkError,\n} from './error'\n\nexport default createClient\n","import type { RetryContext } from './types.js'\n\nexport type RetryDelay = number | ((ctx: RetryContext) => number)\n\nexport const defaultDelay: RetryDelay = (ctx) => {\n const retryAfter = ctx.response?.headers.get('Retry-After')\n if (retryAfter) {\n const seconds = parseInt(retryAfter, 10)\n if (!isNaN(seconds)) return seconds * 1000\n const date = Date.parse(retryAfter)\n if (!isNaN(date)) return Math.max(0, date - Date.now())\n }\n return 2 ** ctx.attempt * 200 + Math.random() * 100\n}\n\nexport async function retry(\n fn: () => Promise<Response>,\n retries: number,\n delay: RetryDelay,\n shouldRetry: (ctx: RetryContext) => boolean = () => true,\n request: Request\n): Promise<Response> {\n let lastErr: unknown\n let lastRes: Response | undefined\n\n for (let i = 0; i <= retries; i++) {\n const ctx: RetryContext = {\n attempt: i + 1,\n request,\n response: lastRes,\n error: lastErr,\n }\n try {\n lastRes = await fn()\n ctx.response = lastRes\n ctx.error = undefined\n if (i < retries && shouldRetry(ctx)) {\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await new Promise((r) => setTimeout(r, wait))\n continue\n }\n return lastRes\n } catch (err) {\n lastErr = err\n ctx.error = err\n if (i === retries || !shouldRetry(ctx)) throw err\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await new Promise((r) => setTimeout(r, wait))\n }\n }\n throw lastErr\n}\n","import { AbortError, CircuitOpenError, TimeoutError } from './error.js'\nimport type { RetryContext } from './types.js'\n\nexport function shouldRetry(ctx: RetryContext): boolean {\n const { error, response } = ctx\n if (\n error instanceof AbortError ||\n error instanceof CircuitOpenError ||\n error instanceof TimeoutError\n )\n return false\n if (!response) return true // network error\n return response.status >= 500 || response.status === 429\n}\n","import { CircuitOpenError, RetryLimitError } from './error.js'\n\nexport class CircuitBreaker {\n private failures = 0\n private nextAttempt = 0\n private isOpen = false\n\n // Returns true if the circuit breaker is currently open (blocking requests).\n get open(): boolean {\n return this.isOpen\n }\n private hooks?: {\n onCircuitOpen?: (req: Request) => void | Promise<void>\n onCircuitClose?: (req: Request) => void | Promise<void>\n }\n private lastSuccessRequest?: Request\n private lastOpenRequest?: Request\n\n constructor(\n private threshold: number,\n private resetTimeout: number\n ) {}\n\n // Call this after each request to record the result and update circuit state.\n // Returns true if the result was counted as a failure.\n recordResult(response?: Response, error?: unknown, req?: Request): boolean {\n // Count thrown errors (network, abort, timeout) as failures\n if (error && !(error instanceof RetryLimitError)) {\n this.setLastOpenRequest(req!)\n this.onFailure()\n return true\n }\n // Count HTTP 5xx and 429 responses as failures\n if (response && (response.status >= 500 || response.status === 429)) {\n this.setLastOpenRequest(req!)\n this.onFailure()\n return true\n }\n // Otherwise, count as success\n if (req) this.setLastSuccessRequest(req)\n this.onSuccess()\n return false\n }\n\n setHooks(hooks: {\n onCircuitOpen?: (req: Request) => void | Promise<void>\n onCircuitClose?: (req: Request) => void | Promise<void>\n }) {\n this.hooks = hooks\n }\n setLastOpenRequest(req: Request) {\n this.lastOpenRequest = req\n }\n\n setLastSuccessRequest(req: Request) {\n this.lastSuccessRequest = req\n }\n\n async invoke<T>(fn: () => Promise<T>): Promise<T> {\n if (Date.now() < this.nextAttempt)\n throw new CircuitOpenError('Circuit is open')\n try {\n const res = await fn()\n this.onSuccess()\n return res\n } catch (err) {\n this.onFailure()\n throw err\n }\n }\n\n private onSuccess() {\n const wasOpen = this.isOpen\n this.failures = 0\n if (wasOpen) {\n this.isOpen = false\n if (this.hooks?.onCircuitClose && this.lastSuccessRequest) {\n this.hooks.onCircuitClose(this.lastSuccessRequest)\n }\n }\n this.lastSuccessRequest = undefined\n }\n\n private onFailure() {\n this.failures++\n if (this.failures >= this.threshold) {\n this.nextAttempt = Date.now() + this.resetTimeout\n this.isOpen = true\n if (this.hooks?.onCircuitOpen && this.lastOpenRequest) {\n this.hooks.onCircuitOpen(this.lastOpenRequest)\n }\n }\n }\n}\n","import type {\n FFetchOptions,\n FFetch,\n FFetchRequestInit,\n PendingRequest,\n} from './types.js'\nimport { retry, defaultDelay } from './retry.js'\nimport { shouldRetry as defaultShouldRetry } from './should-retry.js'\nimport { CircuitBreaker } from './circuit.js'\nimport {\n TimeoutError,\n CircuitOpenError,\n AbortError,\n RetryLimitError,\n NetworkError,\n} from './error.js'\n\nexport function createClient(opts: FFetchOptions = {}): FFetch {\n const {\n timeout: clientDefaultTimeout = 5_000,\n retries: clientDefaultRetries = 0,\n retryDelay: clientDefaultRetryDelay = defaultDelay,\n shouldRetry: clientDefaultShouldRetry = defaultShouldRetry,\n hooks: clientDefaultHooks = {},\n circuit: clientDefaultCircuit,\n fetchHandler,\n } = opts\n\n const breaker = clientDefaultCircuit\n ? new CircuitBreaker(\n clientDefaultCircuit.threshold,\n clientDefaultCircuit.reset\n )\n : null\n\n if (\n breaker &&\n (clientDefaultHooks.onCircuitClose || clientDefaultHooks.onCircuitOpen)\n ) {\n breaker.setHooks({\n onCircuitClose: clientDefaultHooks.onCircuitClose,\n onCircuitOpen: clientDefaultHooks.onCircuitOpen,\n })\n }\n\n const pendingRequests: PendingRequest[] = []\n\n // Helper to abort all pending requests\n function abortAll() {\n for (const entry of pendingRequests) {\n entry.controller?.abort()\n }\n }\n\n const client = async (\n input: RequestInfo | URL,\n init: FFetchRequestInit = {}\n ) => {\n // No longer require AbortSignal.timeout - we'll implement it manually if needed\n let request = new Request(input, init)\n\n // Merge hooks: per-request hooks override client hooks, but fallback to client hooks\n const effectiveHooks = { ...clientDefaultHooks, ...(init.hooks || {}) }\n if (effectiveHooks.transformRequest) {\n request = await effectiveHooks.transformRequest(request)\n }\n await effectiveHooks.before?.(request)\n\n // Determine throwOnHttpError (per-request overrides client default)\n const effectiveThrowOnHttpError =\n typeof init.throwOnHttpError !== 'undefined'\n ? init.throwOnHttpError\n : (opts.throwOnHttpError ?? false)\n\n // Create timeout signal (manual implementation if AbortSignal.timeout not available)\n function createTimeoutSignal(timeout: number): AbortSignal {\n if (typeof AbortSignal?.timeout === 'function') {\n return AbortSignal.timeout(timeout)\n }\n\n // Manual implementation for older environments\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n\n // Clean up timeout if signal is aborted early by other means\n controller.signal.addEventListener(\n 'abort',\n () => clearTimeout(timeoutId),\n { once: true }\n )\n\n return controller.signal\n }\n\n // AbortSignal.timeout/any logic ---\n const effectiveTimeout = init.timeout ?? clientDefaultTimeout\n const userSignal = init.signal\n const transformedSignal = request.signal // Extract signal from transformed request\n let timeoutSignal: AbortSignal | undefined = undefined\n let combinedSignal: AbortSignal | undefined = undefined\n let controller: AbortController | undefined = undefined\n\n if (effectiveTimeout > 0) {\n timeoutSignal = createTimeoutSignal(effectiveTimeout)\n }\n\n // Collect all signals that need to be combined\n const signals: AbortSignal[] = []\n if (userSignal) signals.push(userSignal)\n if (transformedSignal && transformedSignal !== userSignal) {\n signals.push(transformedSignal)\n }\n if (timeoutSignal) signals.push(timeoutSignal)\n\n // Use AbortSignal.any for signal combination. Requires native support or a polyfill.\n // If not available, instruct users to install a polyfill for environments lacking AbortSignal.any.\n // there are always 1 or more signals\n if (signals.length === 1) {\n combinedSignal = signals[0]\n controller = new AbortController()\n } else {\n if (typeof AbortSignal.any !== 'function') {\n throw new Error(\n 'AbortSignal.any is required for combining multiple signals. Please install a polyfill for environments that do not support it.'\n )\n }\n combinedSignal = AbortSignal.any(signals)\n controller = new AbortController()\n }\n const retryWithHooks = async () => {\n const effectiveRetries = init.retries ?? clientDefaultRetries\n const effectiveRetryDelay =\n typeof init.retryDelay !== 'undefined'\n ? init.retryDelay\n : clientDefaultRetryDelay\n const effectiveShouldRetry = init.shouldRetry ?? clientDefaultShouldRetry\n\n // Wrap shouldRetry to call onRetry hook\n let attempt = 0\n const shouldRetryWithHook = (ctx: import('./types').RetryContext) => {\n attempt = ctx.attempt\n const retrying = effectiveShouldRetry(ctx)\n if (retrying && attempt <= effectiveRetries) {\n effectiveHooks.onRetry?.(\n request,\n attempt - 1,\n ctx.error,\n ctx.response\n )\n }\n return retrying\n }\n\n let lastResponse: Response | undefined = undefined\n try {\n let res = await retry(\n async () => {\n // Check for aborts before making the request\n if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(request)\n // Throw AbortError for user aborts, no cause needed\n throw new AbortError('Request was aborted by user')\n }\n if (timeoutSignal?.aborted) {\n effectiveHooks.onTimeout?.(request)\n throw new TimeoutError('signal timed out')\n }\n if (typeof combinedSignal?.throwIfAborted === 'function') {\n combinedSignal.throwIfAborted()\n } else if (combinedSignal?.aborted) {\n if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(request)\n throw new AbortError('Request was aborted by user')\n } else if (timeoutSignal?.aborted) {\n effectiveHooks.onTimeout?.(request)\n throw new TimeoutError('signal timed out')\n } else {\n throw new AbortError(\n 'Request was aborted',\n new DOMException('Aborted', 'AbortError')\n )\n }\n }\n const reqWithSignal = new Request(request, {\n signal: combinedSignal,\n })\n try {\n const handler = fetchHandler ?? fetch\n const response = await handler(reqWithSignal)\n lastResponse = response\n if (\n breaker &&\n (response.status >= 500 || response.status === 429)\n ) {\n breaker.recordResult(response, undefined, request)\n }\n return response\n } catch (err) {\n if (breaker) breaker.recordResult(undefined, err, request)\n if (err instanceof DOMException && err.name === 'AbortError') {\n if (\n timeoutSignal?.aborted &&\n (!userSignal || !userSignal.aborted)\n ) {\n effectiveHooks.onTimeout?.(request)\n throw new TimeoutError('signal timed out', err)\n } else if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(request)\n throw new AbortError('Request was aborted by user')\n } else {\n throw new AbortError(\n 'Request was aborted',\n new DOMException('Aborted', 'AbortError')\n )\n }\n } else if (\n err instanceof TypeError &&\n /NetworkError|network error|failed to fetch|lost connection|NetworkError when attempting to fetch resource/i.test(\n err.message\n )\n ) {\n throw new NetworkError(err.message, err)\n }\n throw err\n }\n },\n effectiveRetries,\n effectiveRetryDelay,\n shouldRetryWithHook,\n request\n )\n if (effectiveHooks.transformResponse) {\n res = await effectiveHooks.transformResponse(res, request)\n }\n await effectiveHooks.after?.(request, res)\n await effectiveHooks.onComplete?.(request, res, undefined)\n // After all retries, if throwOnHttpError is true and final response is error, throw HttpError\n if (\n effectiveThrowOnHttpError &&\n ((res.status >= 400 && res.status < 500 && res.status !== 429) ||\n res.status >= 500 ||\n res.status === 429)\n ) {\n const { HttpError } = await import('./error.js')\n throw new HttpError(\n `HTTP error: ${res.status} ${res.statusText}`,\n res\n )\n }\n return res\n } catch (err: unknown) {\n // If lastResponse is set (any status), return or throw as needed\n if (lastResponse) {\n const resp = lastResponse as Response\n // Only throw if throwOnHttpError is true and final response is 4xx (not 429), 5xx, or 429\n if (\n effectiveThrowOnHttpError &&\n ((resp.status >= 400 && resp.status < 500 && resp.status !== 429) ||\n resp.status >= 500 ||\n resp.status === 429)\n ) {\n const { HttpError } = await import('./error.js')\n throw new HttpError(\n `HTTP error: ${resp.status} ${resp.statusText}`,\n resp\n )\n }\n return resp\n }\n // If the error is a known error type, re-throw it directly and call correct hook\n if (err instanceof TimeoutError) {\n await effectiveHooks.onTimeout?.(request)\n await effectiveHooks.onError?.(request, err)\n await effectiveHooks.onComplete?.(request, undefined, err)\n throw err\n }\n if (err instanceof AbortError) {\n await effectiveHooks.onAbort?.(request)\n await effectiveHooks.onError?.(request, err)\n await effectiveHooks.onComplete?.(request, undefined, err)\n throw err\n }\n if (err instanceof NetworkError) {\n await effectiveHooks.onError?.(request, err)\n await effectiveHooks.onComplete?.(request, undefined, err)\n throw err\n }\n // Otherwise, wrap in RetryLimitError\n const retryErr = new RetryLimitError(\n typeof err === 'object' &&\n err &&\n 'message' in err &&\n typeof (err as { message?: unknown }).message === 'string'\n ? (err as { message: string }).message\n : 'Retry limit reached',\n err\n )\n await effectiveHooks.onError?.(request, retryErr)\n await effectiveHooks.onComplete?.(request, undefined, retryErr)\n throw retryErr\n }\n }\n\n const promise = breaker\n ? breaker.invoke(retryWithHooks).catch(async (err: unknown) => {\n if (err instanceof CircuitOpenError) {\n await effectiveHooks.onCircuitOpen?.(request)\n await effectiveHooks.onError?.(request, err)\n await effectiveHooks.onComplete?.(request, undefined, err)\n } else {\n await effectiveHooks.onError?.(request, err)\n await effectiveHooks.onComplete?.(request, undefined, err)\n }\n throw err\n })\n : retryWithHooks()\n\n const entry: PendingRequest = {\n promise,\n request,\n controller,\n }\n pendingRequests.push(entry)\n\n return promise.finally(() => {\n const index = pendingRequests.indexOf(entry)\n if (index > -1) {\n pendingRequests.splice(index, 1)\n }\n })\n }\n\n // Add pendingRequests property to the client function (read-only)\n Object.defineProperty(client, 'pendingRequests', {\n get() {\n return pendingRequests\n },\n enumerable: false,\n configurable: false,\n })\n\n // Add abortAll method to the client function (read-only)\n Object.defineProperty(client, 'abortAll', {\n value: abortAll,\n writable: false,\n enumerable: false,\n configurable: false,\n })\n\n // Expose circuit breaker open state\n Object.defineProperty(client, 'circuitOpen', {\n get() {\n return breaker ? breaker.open : false\n },\n enumerable: true,\n })\n\n return client as FFetch\n}\n"],"mappings":"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","names":["shouldRetry","controller","HttpError"]}
|
package/dist/index.d.cts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AbortError,
|
|
3
|
+
CircuitOpenError,
|
|
4
|
+
NetworkError,
|
|
5
|
+
RetryLimitError,
|
|
6
|
+
TimeoutError
|
|
7
|
+
} from "./chunk-RV6LC73N.js";
|
|
8
|
+
|
|
1
9
|
// src/retry.ts
|
|
2
10
|
var defaultDelay = (ctx) => {
|
|
3
11
|
const retryAfter = ctx.response?.headers.get("Retry-After");
|
|
@@ -40,40 +48,6 @@ async function retry(fn, retries, delay, shouldRetry2 = () => true, request) {
|
|
|
40
48
|
throw lastErr;
|
|
41
49
|
}
|
|
42
50
|
|
|
43
|
-
// src/error.ts
|
|
44
|
-
var BaseError = class extends Error {
|
|
45
|
-
constructor(name, message, cause) {
|
|
46
|
-
super(message);
|
|
47
|
-
this.name = name;
|
|
48
|
-
if (cause !== void 0) this.cause = cause;
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
var TimeoutError = class extends BaseError {
|
|
52
|
-
constructor(message = "Request timed out", cause) {
|
|
53
|
-
super("TimeoutError", message, cause);
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
var CircuitOpenError = class extends BaseError {
|
|
57
|
-
constructor(message = "Circuit is open", cause) {
|
|
58
|
-
super("CircuitOpenError", message, cause);
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
var AbortError = class extends BaseError {
|
|
62
|
-
constructor(message = "Request was aborted", cause) {
|
|
63
|
-
super("AbortError", message, cause);
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
var RetryLimitError = class extends BaseError {
|
|
67
|
-
constructor(message = "Retry limit reached", cause) {
|
|
68
|
-
super("RetryLimitError", message, cause);
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
var NetworkError = class extends BaseError {
|
|
72
|
-
constructor(message = "Network error occurred", cause) {
|
|
73
|
-
super("NetworkError", message, cause);
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
|
|
77
51
|
// src/should-retry.ts
|
|
78
52
|
function shouldRetry(ctx) {
|
|
79
53
|
const { error, response } = ctx;
|
|
@@ -191,6 +165,7 @@ function createClient(opts = {}) {
|
|
|
191
165
|
request = await effectiveHooks.transformRequest(request);
|
|
192
166
|
}
|
|
193
167
|
await effectiveHooks.before?.(request);
|
|
168
|
+
const effectiveThrowOnHttpError = typeof init.throwOnHttpError !== "undefined" ? init.throwOnHttpError : opts.throwOnHttpError ?? false;
|
|
194
169
|
function createTimeoutSignal(timeout) {
|
|
195
170
|
if (typeof AbortSignal?.timeout === "function") {
|
|
196
171
|
return AbortSignal.timeout(timeout);
|
|
@@ -249,54 +224,33 @@ function createClient(opts = {}) {
|
|
|
249
224
|
}
|
|
250
225
|
return retrying;
|
|
251
226
|
};
|
|
252
|
-
|
|
253
|
-
if (err instanceof DOMException && err.name === "AbortError") {
|
|
254
|
-
if (timeoutSignal?.aborted && (!userSignal || !userSignal.aborted)) {
|
|
255
|
-
return new TimeoutError("signal timed out", err);
|
|
256
|
-
} else {
|
|
257
|
-
return new AbortError("Request was aborted", err);
|
|
258
|
-
}
|
|
259
|
-
} else if (err instanceof TypeError && /NetworkError|network error|failed to fetch|lost connection|NetworkError when attempting to fetch resource/i.test(
|
|
260
|
-
err.message
|
|
261
|
-
)) {
|
|
262
|
-
return new NetworkError(err.message, err);
|
|
263
|
-
}
|
|
264
|
-
return err;
|
|
265
|
-
}
|
|
266
|
-
async function handleError(err) {
|
|
267
|
-
err = mapToCustomError(err);
|
|
268
|
-
if (userSignal?.aborted) {
|
|
269
|
-
const abortErr = new AbortError("Request was aborted by user");
|
|
270
|
-
await effectiveHooks.onAbort?.(request);
|
|
271
|
-
await effectiveHooks.onError?.(request, abortErr);
|
|
272
|
-
await effectiveHooks.onComplete?.(request, void 0, abortErr);
|
|
273
|
-
throw abortErr;
|
|
274
|
-
}
|
|
275
|
-
if (err instanceof TimeoutError || err instanceof NetworkError || err instanceof AbortError) {
|
|
276
|
-
if (err instanceof TimeoutError) {
|
|
277
|
-
await effectiveHooks.onTimeout?.(request);
|
|
278
|
-
}
|
|
279
|
-
if (err instanceof AbortError) {
|
|
280
|
-
await effectiveHooks.onAbort?.(request);
|
|
281
|
-
}
|
|
282
|
-
await effectiveHooks.onError?.(request, err);
|
|
283
|
-
await effectiveHooks.onComplete?.(request, void 0, err);
|
|
284
|
-
throw err;
|
|
285
|
-
}
|
|
286
|
-
const retryErr = new RetryLimitError(
|
|
287
|
-
typeof err === "object" && err && "message" in err && typeof err.message === "string" ? err.message : "Retry limit reached"
|
|
288
|
-
);
|
|
289
|
-
await effectiveHooks.onError?.(request, retryErr);
|
|
290
|
-
await effectiveHooks.onComplete?.(request, void 0, retryErr);
|
|
291
|
-
throw retryErr;
|
|
292
|
-
}
|
|
227
|
+
let lastResponse = void 0;
|
|
293
228
|
try {
|
|
294
229
|
let res = await retry(
|
|
295
230
|
async () => {
|
|
231
|
+
if (userSignal?.aborted) {
|
|
232
|
+
effectiveHooks.onAbort?.(request);
|
|
233
|
+
throw new AbortError("Request was aborted by user");
|
|
234
|
+
}
|
|
235
|
+
if (timeoutSignal?.aborted) {
|
|
236
|
+
effectiveHooks.onTimeout?.(request);
|
|
237
|
+
throw new TimeoutError("signal timed out");
|
|
238
|
+
}
|
|
296
239
|
if (typeof combinedSignal?.throwIfAborted === "function") {
|
|
297
240
|
combinedSignal.throwIfAborted();
|
|
298
241
|
} else if (combinedSignal?.aborted) {
|
|
299
|
-
|
|
242
|
+
if (userSignal?.aborted) {
|
|
243
|
+
effectiveHooks.onAbort?.(request);
|
|
244
|
+
throw new AbortError("Request was aborted by user");
|
|
245
|
+
} else if (timeoutSignal?.aborted) {
|
|
246
|
+
effectiveHooks.onTimeout?.(request);
|
|
247
|
+
throw new TimeoutError("signal timed out");
|
|
248
|
+
} else {
|
|
249
|
+
throw new AbortError(
|
|
250
|
+
"Request was aborted",
|
|
251
|
+
new DOMException("Aborted", "AbortError")
|
|
252
|
+
);
|
|
253
|
+
}
|
|
300
254
|
}
|
|
301
255
|
const reqWithSignal = new Request(request, {
|
|
302
256
|
signal: combinedSignal
|
|
@@ -304,15 +258,32 @@ function createClient(opts = {}) {
|
|
|
304
258
|
try {
|
|
305
259
|
const handler = fetchHandler ?? fetch;
|
|
306
260
|
const response = await handler(reqWithSignal);
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
}
|
|
261
|
+
lastResponse = response;
|
|
262
|
+
if (breaker && (response.status >= 500 || response.status === 429)) {
|
|
263
|
+
breaker.recordResult(response, void 0, request);
|
|
311
264
|
}
|
|
312
265
|
return response;
|
|
313
266
|
} catch (err) {
|
|
314
267
|
if (breaker) breaker.recordResult(void 0, err, request);
|
|
315
|
-
|
|
268
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
269
|
+
if (timeoutSignal?.aborted && (!userSignal || !userSignal.aborted)) {
|
|
270
|
+
effectiveHooks.onTimeout?.(request);
|
|
271
|
+
throw new TimeoutError("signal timed out", err);
|
|
272
|
+
} else if (userSignal?.aborted) {
|
|
273
|
+
effectiveHooks.onAbort?.(request);
|
|
274
|
+
throw new AbortError("Request was aborted by user");
|
|
275
|
+
} else {
|
|
276
|
+
throw new AbortError(
|
|
277
|
+
"Request was aborted",
|
|
278
|
+
new DOMException("Aborted", "AbortError")
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
} else if (err instanceof TypeError && /NetworkError|network error|failed to fetch|lost connection|NetworkError when attempting to fetch resource/i.test(
|
|
282
|
+
err.message
|
|
283
|
+
)) {
|
|
284
|
+
throw new NetworkError(err.message, err);
|
|
285
|
+
}
|
|
286
|
+
throw err;
|
|
316
287
|
}
|
|
317
288
|
},
|
|
318
289
|
effectiveRetries,
|
|
@@ -325,10 +296,50 @@ function createClient(opts = {}) {
|
|
|
325
296
|
}
|
|
326
297
|
await effectiveHooks.after?.(request, res);
|
|
327
298
|
await effectiveHooks.onComplete?.(request, res, void 0);
|
|
299
|
+
if (effectiveThrowOnHttpError && (res.status >= 400 && res.status < 500 && res.status !== 429 || res.status >= 500 || res.status === 429)) {
|
|
300
|
+
const { HttpError } = await import("./error-XBHPSMAQ.js");
|
|
301
|
+
throw new HttpError(
|
|
302
|
+
`HTTP error: ${res.status} ${res.statusText}`,
|
|
303
|
+
res
|
|
304
|
+
);
|
|
305
|
+
}
|
|
328
306
|
return res;
|
|
329
307
|
} catch (err) {
|
|
330
|
-
|
|
331
|
-
|
|
308
|
+
if (lastResponse) {
|
|
309
|
+
const resp = lastResponse;
|
|
310
|
+
if (effectiveThrowOnHttpError && (resp.status >= 400 && resp.status < 500 && resp.status !== 429 || resp.status >= 500 || resp.status === 429)) {
|
|
311
|
+
const { HttpError } = await import("./error-XBHPSMAQ.js");
|
|
312
|
+
throw new HttpError(
|
|
313
|
+
`HTTP error: ${resp.status} ${resp.statusText}`,
|
|
314
|
+
resp
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
return resp;
|
|
318
|
+
}
|
|
319
|
+
if (err instanceof TimeoutError) {
|
|
320
|
+
await effectiveHooks.onTimeout?.(request);
|
|
321
|
+
await effectiveHooks.onError?.(request, err);
|
|
322
|
+
await effectiveHooks.onComplete?.(request, void 0, err);
|
|
323
|
+
throw err;
|
|
324
|
+
}
|
|
325
|
+
if (err instanceof AbortError) {
|
|
326
|
+
await effectiveHooks.onAbort?.(request);
|
|
327
|
+
await effectiveHooks.onError?.(request, err);
|
|
328
|
+
await effectiveHooks.onComplete?.(request, void 0, err);
|
|
329
|
+
throw err;
|
|
330
|
+
}
|
|
331
|
+
if (err instanceof NetworkError) {
|
|
332
|
+
await effectiveHooks.onError?.(request, err);
|
|
333
|
+
await effectiveHooks.onComplete?.(request, void 0, err);
|
|
334
|
+
throw err;
|
|
335
|
+
}
|
|
336
|
+
const retryErr = new RetryLimitError(
|
|
337
|
+
typeof err === "object" && err && "message" in err && typeof err.message === "string" ? err.message : "Retry limit reached",
|
|
338
|
+
err
|
|
339
|
+
);
|
|
340
|
+
await effectiveHooks.onError?.(request, retryErr);
|
|
341
|
+
await effectiveHooks.onComplete?.(request, void 0, retryErr);
|
|
342
|
+
throw retryErr;
|
|
332
343
|
}
|
|
333
344
|
};
|
|
334
345
|
const promise = breaker ? breaker.invoke(retryWithHooks).catch(async (err) => {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/retry.ts","../src/error.ts","../src/should-retry.ts","../src/circuit.ts","../src/client.ts","../src/index.ts"],"sourcesContent":["import type { RetryContext } from './types.js'\n\nexport type RetryDelay = number | ((ctx: RetryContext) => number)\n\nexport const defaultDelay: RetryDelay = (ctx) => {\n const retryAfter = ctx.response?.headers.get('Retry-After')\n if (retryAfter) {\n const seconds = parseInt(retryAfter, 10)\n if (!isNaN(seconds)) return seconds * 1000\n const date = Date.parse(retryAfter)\n if (!isNaN(date)) return Math.max(0, date - Date.now())\n }\n return 2 ** ctx.attempt * 200 + Math.random() * 100\n}\n\nexport async function retry(\n fn: () => Promise<Response>,\n retries: number,\n delay: RetryDelay,\n shouldRetry: (ctx: RetryContext) => boolean = () => true,\n request: Request\n): Promise<Response> {\n let lastErr: unknown\n let lastRes: Response | undefined\n\n for (let i = 0; i <= retries; i++) {\n const ctx: RetryContext = {\n attempt: i + 1,\n request,\n response: lastRes,\n error: lastErr,\n }\n try {\n lastRes = await fn()\n ctx.response = lastRes\n ctx.error = undefined\n if (i < retries && shouldRetry(ctx)) {\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await new Promise((r) => setTimeout(r, wait))\n continue\n }\n return lastRes\n } catch (err) {\n lastErr = err\n ctx.error = err\n if (i === retries || !shouldRetry(ctx)) throw err\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await new Promise((r) => setTimeout(r, wait))\n }\n }\n throw lastErr\n}\n","// Base error class to reduce duplication\nclass BaseError extends Error {\n public cause?: unknown\n constructor(name: string, message: string, cause?: unknown) {\n super(message)\n this.name = name\n if (cause !== undefined) this.cause = cause\n }\n}\n\nexport class TimeoutError extends BaseError {\n constructor(message = 'Request timed out', cause?: unknown) {\n super('TimeoutError', message, cause)\n }\n}\n\nexport class CircuitOpenError extends BaseError {\n constructor(message = 'Circuit is open', cause?: unknown) {\n super('CircuitOpenError', message, cause)\n }\n}\n\nexport class AbortError extends BaseError {\n constructor(message = 'Request was aborted', cause?: unknown) {\n super('AbortError', message, cause)\n }\n}\n\nexport class RetryLimitError extends BaseError {\n constructor(message = 'Retry limit reached', cause?: unknown) {\n super('RetryLimitError', message, cause)\n }\n}\n\nexport class NetworkError extends BaseError {\n constructor(message = 'Network error occurred', cause?: unknown) {\n super('NetworkError', message, cause)\n }\n}\n","import { AbortError, CircuitOpenError, TimeoutError } from './error.js'\nimport type { RetryContext } from './types.js'\n\nexport function shouldRetry(ctx: RetryContext): boolean {\n const { error, response } = ctx\n if (\n error instanceof AbortError ||\n error instanceof CircuitOpenError ||\n error instanceof TimeoutError\n )\n return false\n if (!response) return true // network error\n return response.status >= 500 || response.status === 429\n}\n","import { CircuitOpenError, RetryLimitError } from './error.js'\n\nexport class CircuitBreaker {\n private failures = 0\n private nextAttempt = 0\n private isOpen = false\n\n // Returns true if the circuit breaker is currently open (blocking requests).\n get open(): boolean {\n return this.isOpen\n }\n private hooks?: {\n onCircuitOpen?: (req: Request) => void | Promise<void>\n onCircuitClose?: (req: Request) => void | Promise<void>\n }\n private lastSuccessRequest?: Request\n private lastOpenRequest?: Request\n\n constructor(\n private threshold: number,\n private resetTimeout: number\n ) {}\n\n // Call this after each request to record the result and update circuit state.\n // Returns true if the result was counted as a failure.\n recordResult(response?: Response, error?: unknown, req?: Request): boolean {\n // Count thrown errors (network, abort, timeout) as failures\n if (error && !(error instanceof RetryLimitError)) {\n this.setLastOpenRequest(req!)\n this.onFailure()\n return true\n }\n // Count HTTP 5xx and 429 responses as failures\n if (response && (response.status >= 500 || response.status === 429)) {\n this.setLastOpenRequest(req!)\n this.onFailure()\n return true\n }\n // Otherwise, count as success\n if (req) this.setLastSuccessRequest(req)\n this.onSuccess()\n return false\n }\n\n setHooks(hooks: {\n onCircuitOpen?: (req: Request) => void | Promise<void>\n onCircuitClose?: (req: Request) => void | Promise<void>\n }) {\n this.hooks = hooks\n }\n setLastOpenRequest(req: Request) {\n this.lastOpenRequest = req\n }\n\n setLastSuccessRequest(req: Request) {\n this.lastSuccessRequest = req\n }\n\n async invoke<T>(fn: () => Promise<T>): Promise<T> {\n if (Date.now() < this.nextAttempt)\n throw new CircuitOpenError('Circuit is open')\n try {\n const res = await fn()\n this.onSuccess()\n return res\n } catch (err) {\n this.onFailure()\n throw err\n }\n }\n\n private onSuccess() {\n const wasOpen = this.isOpen\n this.failures = 0\n if (wasOpen) {\n this.isOpen = false\n if (this.hooks?.onCircuitClose && this.lastSuccessRequest) {\n this.hooks.onCircuitClose(this.lastSuccessRequest)\n }\n }\n this.lastSuccessRequest = undefined\n }\n\n private onFailure() {\n this.failures++\n if (this.failures >= this.threshold) {\n this.nextAttempt = Date.now() + this.resetTimeout\n this.isOpen = true\n if (this.hooks?.onCircuitOpen && this.lastOpenRequest) {\n this.hooks.onCircuitOpen(this.lastOpenRequest)\n }\n }\n }\n}\n","import type {\n FFetchOptions,\n FFetch,\n FFetchRequestInit,\n PendingRequest,\n} from './types.js'\nimport { retry, defaultDelay } from './retry.js'\nimport { shouldRetry as defaultShouldRetry } from './should-retry.js'\nimport { CircuitBreaker } from './circuit.js'\nimport {\n TimeoutError,\n CircuitOpenError,\n AbortError,\n RetryLimitError,\n NetworkError,\n} from './error.js'\n\nexport function createClient(opts: FFetchOptions = {}): FFetch {\n const {\n timeout: clientDefaultTimeout = 5_000,\n retries: clientDefaultRetries = 0,\n retryDelay: clientDefaultRetryDelay = defaultDelay,\n shouldRetry: clientDefaultShouldRetry = defaultShouldRetry,\n hooks: clientDefaultHooks = {},\n circuit: clientDefaultCircuit,\n fetchHandler,\n } = opts\n\n const breaker = clientDefaultCircuit\n ? new CircuitBreaker(\n clientDefaultCircuit.threshold,\n clientDefaultCircuit.reset\n )\n : null\n\n if (\n breaker &&\n (clientDefaultHooks.onCircuitClose || clientDefaultHooks.onCircuitOpen)\n ) {\n breaker.setHooks({\n onCircuitClose: clientDefaultHooks.onCircuitClose,\n onCircuitOpen: clientDefaultHooks.onCircuitOpen,\n })\n }\n\n const pendingRequests: PendingRequest[] = []\n\n // Helper to abort all pending requests\n function abortAll() {\n for (const entry of pendingRequests) {\n entry.controller?.abort()\n }\n }\n\n const client = async (\n input: RequestInfo | URL,\n init: FFetchRequestInit = {}\n ) => {\n // No longer require AbortSignal.timeout - we'll implement it manually if needed\n let request = new Request(input, init)\n\n // Merge hooks: per-request hooks override client hooks, but fallback to client hooks\n const effectiveHooks = { ...clientDefaultHooks, ...(init.hooks || {}) }\n if (effectiveHooks.transformRequest) {\n request = await effectiveHooks.transformRequest(request)\n }\n await effectiveHooks.before?.(request)\n\n // Create timeout signal (manual implementation if AbortSignal.timeout not available)\n function createTimeoutSignal(timeout: number): AbortSignal {\n if (typeof AbortSignal?.timeout === 'function') {\n return AbortSignal.timeout(timeout)\n }\n\n // Manual implementation for older environments\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n\n // Clean up timeout if signal is aborted early by other means\n controller.signal.addEventListener(\n 'abort',\n () => clearTimeout(timeoutId),\n { once: true }\n )\n\n return controller.signal\n }\n\n // AbortSignal.timeout/any logic ---\n const effectiveTimeout = init.timeout ?? clientDefaultTimeout\n const userSignal = init.signal\n const transformedSignal = request.signal // Extract signal from transformed request\n let timeoutSignal: AbortSignal | undefined = undefined\n let combinedSignal: AbortSignal | undefined = undefined\n let controller: AbortController | undefined = undefined\n\n if (effectiveTimeout > 0) {\n timeoutSignal = createTimeoutSignal(effectiveTimeout)\n }\n\n // Collect all signals that need to be combined\n const signals: AbortSignal[] = []\n if (userSignal) signals.push(userSignal)\n if (transformedSignal && transformedSignal !== userSignal) {\n signals.push(transformedSignal)\n }\n if (timeoutSignal) signals.push(timeoutSignal)\n\n // Use AbortSignal.any for signal combination. Requires native support or a polyfill.\n // If not available, instruct users to install a polyfill for environments lacking AbortSignal.any.\n // there are always 1 or more signals\n if (signals.length === 1) {\n combinedSignal = signals[0]\n controller = new AbortController()\n } else {\n if (typeof AbortSignal.any !== 'function') {\n throw new Error(\n 'AbortSignal.any is required for combining multiple signals. Please install a polyfill for environments that do not support it.'\n )\n }\n combinedSignal = AbortSignal.any(signals)\n controller = new AbortController()\n }\n const retryWithHooks = async () => {\n const effectiveRetries = init.retries ?? clientDefaultRetries\n const effectiveRetryDelay =\n typeof init.retryDelay !== 'undefined'\n ? init.retryDelay\n : clientDefaultRetryDelay\n const effectiveShouldRetry = init.shouldRetry ?? clientDefaultShouldRetry\n\n // Wrap shouldRetry to call onRetry hook\n let attempt = 0\n const shouldRetryWithHook = (ctx: import('./types').RetryContext) => {\n attempt = ctx.attempt\n const retrying = effectiveShouldRetry(ctx)\n if (retrying && attempt <= effectiveRetries) {\n effectiveHooks.onRetry?.(\n request,\n attempt - 1,\n ctx.error,\n ctx.response\n )\n }\n return retrying\n }\n\n function mapToCustomError(err: unknown): unknown {\n if (err instanceof DOMException && err.name === 'AbortError') {\n if (timeoutSignal?.aborted && (!userSignal || !userSignal.aborted)) {\n return new TimeoutError('signal timed out', err)\n } else {\n return new AbortError('Request was aborted', err)\n }\n } else if (\n err instanceof TypeError &&\n /NetworkError|network error|failed to fetch|lost connection|NetworkError when attempting to fetch resource/i.test(\n err.message\n )\n ) {\n return new NetworkError(err.message, err)\n }\n return err\n }\n\n async function handleError(err: unknown): Promise<never> {\n err = mapToCustomError(err)\n // If user aborted, always throw AbortError, not RetryLimitError\n if (userSignal?.aborted) {\n const abortErr = new AbortError('Request was aborted by user')\n await effectiveHooks.onAbort?.(request)\n await effectiveHooks.onError?.(request, abortErr)\n await effectiveHooks.onComplete?.(request, undefined, abortErr)\n throw abortErr\n }\n // If the error is a custom error, re-throw it directly (do not wrap)\n if (\n err instanceof TimeoutError ||\n err instanceof NetworkError ||\n err instanceof AbortError\n ) {\n if (err instanceof TimeoutError) {\n await effectiveHooks.onTimeout?.(request)\n }\n if (err instanceof AbortError) {\n await effectiveHooks.onAbort?.(request)\n }\n await effectiveHooks.onError?.(request, err)\n await effectiveHooks.onComplete?.(request, undefined, err)\n throw err\n }\n // Otherwise, throw RetryLimitError after all retries are exhausted\n const retryErr = new RetryLimitError(\n typeof err === 'object' &&\n err &&\n 'message' in err &&\n typeof (err as { message?: unknown }).message === 'string'\n ? (err as { message: string }).message\n : 'Retry limit reached'\n )\n await effectiveHooks.onError?.(request, retryErr)\n await effectiveHooks.onComplete?.(request, undefined, retryErr)\n throw retryErr\n }\n\n try {\n let res = await retry(\n async () => {\n // Use AbortSignal.throwIfAborted() before starting fetch\n if (typeof combinedSignal?.throwIfAborted === 'function') {\n combinedSignal.throwIfAborted()\n } else if (combinedSignal?.aborted) {\n throw new AbortError('Request was aborted')\n }\n const reqWithSignal = new Request(request, {\n signal: combinedSignal,\n })\n try {\n const handler = fetchHandler ?? fetch\n const response = await handler(reqWithSignal)\n // Circuit breaker: record result\n if (breaker) {\n if (breaker.recordResult(response, undefined, request)) {\n throw new Error(`HTTP error: ${response.status}`)\n }\n }\n return response\n } catch (err) {\n if (breaker) breaker.recordResult(undefined, err, request)\n throw mapToCustomError(err)\n }\n },\n effectiveRetries,\n effectiveRetryDelay,\n shouldRetryWithHook,\n request\n )\n if (effectiveHooks.transformResponse) {\n res = await effectiveHooks.transformResponse(res, request)\n }\n await effectiveHooks.after?.(request, res)\n await effectiveHooks.onComplete?.(request, res, undefined)\n return res\n } catch (err: unknown) {\n await handleError(err)\n throw new Error('Unreachable: handleError should always throw')\n }\n }\n\n const promise = breaker\n ? breaker.invoke(retryWithHooks).catch(async (err: unknown) => {\n if (err instanceof CircuitOpenError) {\n await effectiveHooks.onCircuitOpen?.(request)\n await effectiveHooks.onError?.(request, err)\n await effectiveHooks.onComplete?.(request, undefined, err)\n } else {\n await effectiveHooks.onError?.(request, err)\n await effectiveHooks.onComplete?.(request, undefined, err)\n }\n throw err\n })\n : retryWithHooks()\n\n const entry: PendingRequest = {\n promise,\n request,\n controller,\n }\n pendingRequests.push(entry)\n\n return promise.finally(() => {\n const index = pendingRequests.indexOf(entry)\n if (index > -1) {\n pendingRequests.splice(index, 1)\n }\n })\n }\n\n // Add pendingRequests property to the client function (read-only)\n Object.defineProperty(client, 'pendingRequests', {\n get() {\n return pendingRequests\n },\n enumerable: false,\n configurable: false,\n })\n\n // Add abortAll method to the client function (read-only)\n Object.defineProperty(client, 'abortAll', {\n value: abortAll,\n writable: false,\n enumerable: false,\n configurable: false,\n })\n\n // Expose circuit breaker open state\n Object.defineProperty(client, 'circuitOpen', {\n get() {\n return breaker ? breaker.open : false\n },\n enumerable: true,\n })\n\n return client as FFetch\n}\n","export type { FFetch, FFetchOptions } from './types'\nexport type { Hooks } from './hooks'\n\nimport { createClient } from './client'\nexport { createClient } from './client'\n\nexport {\n TimeoutError,\n CircuitOpenError,\n AbortError,\n RetryLimitError,\n NetworkError,\n} from './error'\n\nexport default createClient\n"],"mappings":"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","names":["shouldRetry","controller"]}
|
|
1
|
+
{"version":3,"sources":["../src/retry.ts","../src/should-retry.ts","../src/circuit.ts","../src/client.ts","../src/index.ts"],"sourcesContent":["import type { RetryContext } from './types.js'\n\nexport type RetryDelay = number | ((ctx: RetryContext) => number)\n\nexport const defaultDelay: RetryDelay = (ctx) => {\n const retryAfter = ctx.response?.headers.get('Retry-After')\n if (retryAfter) {\n const seconds = parseInt(retryAfter, 10)\n if (!isNaN(seconds)) return seconds * 1000\n const date = Date.parse(retryAfter)\n if (!isNaN(date)) return Math.max(0, date - Date.now())\n }\n return 2 ** ctx.attempt * 200 + Math.random() * 100\n}\n\nexport async function retry(\n fn: () => Promise<Response>,\n retries: number,\n delay: RetryDelay,\n shouldRetry: (ctx: RetryContext) => boolean = () => true,\n request: Request\n): Promise<Response> {\n let lastErr: unknown\n let lastRes: Response | undefined\n\n for (let i = 0; i <= retries; i++) {\n const ctx: RetryContext = {\n attempt: i + 1,\n request,\n response: lastRes,\n error: lastErr,\n }\n try {\n lastRes = await fn()\n ctx.response = lastRes\n ctx.error = undefined\n if (i < retries && shouldRetry(ctx)) {\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await new Promise((r) => setTimeout(r, wait))\n continue\n }\n return lastRes\n } catch (err) {\n lastErr = err\n ctx.error = err\n if (i === retries || !shouldRetry(ctx)) throw err\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await new Promise((r) => setTimeout(r, wait))\n }\n }\n throw lastErr\n}\n","import { AbortError, CircuitOpenError, TimeoutError } from './error.js'\nimport type { RetryContext } from './types.js'\n\nexport function shouldRetry(ctx: RetryContext): boolean {\n const { error, response } = ctx\n if (\n error instanceof AbortError ||\n error instanceof CircuitOpenError ||\n error instanceof TimeoutError\n )\n return false\n if (!response) return true // network error\n return response.status >= 500 || response.status === 429\n}\n","import { CircuitOpenError, RetryLimitError } from './error.js'\n\nexport class CircuitBreaker {\n private failures = 0\n private nextAttempt = 0\n private isOpen = false\n\n // Returns true if the circuit breaker is currently open (blocking requests).\n get open(): boolean {\n return this.isOpen\n }\n private hooks?: {\n onCircuitOpen?: (req: Request) => void | Promise<void>\n onCircuitClose?: (req: Request) => void | Promise<void>\n }\n private lastSuccessRequest?: Request\n private lastOpenRequest?: Request\n\n constructor(\n private threshold: number,\n private resetTimeout: number\n ) {}\n\n // Call this after each request to record the result and update circuit state.\n // Returns true if the result was counted as a failure.\n recordResult(response?: Response, error?: unknown, req?: Request): boolean {\n // Count thrown errors (network, abort, timeout) as failures\n if (error && !(error instanceof RetryLimitError)) {\n this.setLastOpenRequest(req!)\n this.onFailure()\n return true\n }\n // Count HTTP 5xx and 429 responses as failures\n if (response && (response.status >= 500 || response.status === 429)) {\n this.setLastOpenRequest(req!)\n this.onFailure()\n return true\n }\n // Otherwise, count as success\n if (req) this.setLastSuccessRequest(req)\n this.onSuccess()\n return false\n }\n\n setHooks(hooks: {\n onCircuitOpen?: (req: Request) => void | Promise<void>\n onCircuitClose?: (req: Request) => void | Promise<void>\n }) {\n this.hooks = hooks\n }\n setLastOpenRequest(req: Request) {\n this.lastOpenRequest = req\n }\n\n setLastSuccessRequest(req: Request) {\n this.lastSuccessRequest = req\n }\n\n async invoke<T>(fn: () => Promise<T>): Promise<T> {\n if (Date.now() < this.nextAttempt)\n throw new CircuitOpenError('Circuit is open')\n try {\n const res = await fn()\n this.onSuccess()\n return res\n } catch (err) {\n this.onFailure()\n throw err\n }\n }\n\n private onSuccess() {\n const wasOpen = this.isOpen\n this.failures = 0\n if (wasOpen) {\n this.isOpen = false\n if (this.hooks?.onCircuitClose && this.lastSuccessRequest) {\n this.hooks.onCircuitClose(this.lastSuccessRequest)\n }\n }\n this.lastSuccessRequest = undefined\n }\n\n private onFailure() {\n this.failures++\n if (this.failures >= this.threshold) {\n this.nextAttempt = Date.now() + this.resetTimeout\n this.isOpen = true\n if (this.hooks?.onCircuitOpen && this.lastOpenRequest) {\n this.hooks.onCircuitOpen(this.lastOpenRequest)\n }\n }\n }\n}\n","import type {\n FFetchOptions,\n FFetch,\n FFetchRequestInit,\n PendingRequest,\n} from './types.js'\nimport { retry, defaultDelay } from './retry.js'\nimport { shouldRetry as defaultShouldRetry } from './should-retry.js'\nimport { CircuitBreaker } from './circuit.js'\nimport {\n TimeoutError,\n CircuitOpenError,\n AbortError,\n RetryLimitError,\n NetworkError,\n} from './error.js'\n\nexport function createClient(opts: FFetchOptions = {}): FFetch {\n const {\n timeout: clientDefaultTimeout = 5_000,\n retries: clientDefaultRetries = 0,\n retryDelay: clientDefaultRetryDelay = defaultDelay,\n shouldRetry: clientDefaultShouldRetry = defaultShouldRetry,\n hooks: clientDefaultHooks = {},\n circuit: clientDefaultCircuit,\n fetchHandler,\n } = opts\n\n const breaker = clientDefaultCircuit\n ? new CircuitBreaker(\n clientDefaultCircuit.threshold,\n clientDefaultCircuit.reset\n )\n : null\n\n if (\n breaker &&\n (clientDefaultHooks.onCircuitClose || clientDefaultHooks.onCircuitOpen)\n ) {\n breaker.setHooks({\n onCircuitClose: clientDefaultHooks.onCircuitClose,\n onCircuitOpen: clientDefaultHooks.onCircuitOpen,\n })\n }\n\n const pendingRequests: PendingRequest[] = []\n\n // Helper to abort all pending requests\n function abortAll() {\n for (const entry of pendingRequests) {\n entry.controller?.abort()\n }\n }\n\n const client = async (\n input: RequestInfo | URL,\n init: FFetchRequestInit = {}\n ) => {\n // No longer require AbortSignal.timeout - we'll implement it manually if needed\n let request = new Request(input, init)\n\n // Merge hooks: per-request hooks override client hooks, but fallback to client hooks\n const effectiveHooks = { ...clientDefaultHooks, ...(init.hooks || {}) }\n if (effectiveHooks.transformRequest) {\n request = await effectiveHooks.transformRequest(request)\n }\n await effectiveHooks.before?.(request)\n\n // Determine throwOnHttpError (per-request overrides client default)\n const effectiveThrowOnHttpError =\n typeof init.throwOnHttpError !== 'undefined'\n ? init.throwOnHttpError\n : (opts.throwOnHttpError ?? false)\n\n // Create timeout signal (manual implementation if AbortSignal.timeout not available)\n function createTimeoutSignal(timeout: number): AbortSignal {\n if (typeof AbortSignal?.timeout === 'function') {\n return AbortSignal.timeout(timeout)\n }\n\n // Manual implementation for older environments\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n\n // Clean up timeout if signal is aborted early by other means\n controller.signal.addEventListener(\n 'abort',\n () => clearTimeout(timeoutId),\n { once: true }\n )\n\n return controller.signal\n }\n\n // AbortSignal.timeout/any logic ---\n const effectiveTimeout = init.timeout ?? clientDefaultTimeout\n const userSignal = init.signal\n const transformedSignal = request.signal // Extract signal from transformed request\n let timeoutSignal: AbortSignal | undefined = undefined\n let combinedSignal: AbortSignal | undefined = undefined\n let controller: AbortController | undefined = undefined\n\n if (effectiveTimeout > 0) {\n timeoutSignal = createTimeoutSignal(effectiveTimeout)\n }\n\n // Collect all signals that need to be combined\n const signals: AbortSignal[] = []\n if (userSignal) signals.push(userSignal)\n if (transformedSignal && transformedSignal !== userSignal) {\n signals.push(transformedSignal)\n }\n if (timeoutSignal) signals.push(timeoutSignal)\n\n // Use AbortSignal.any for signal combination. Requires native support or a polyfill.\n // If not available, instruct users to install a polyfill for environments lacking AbortSignal.any.\n // there are always 1 or more signals\n if (signals.length === 1) {\n combinedSignal = signals[0]\n controller = new AbortController()\n } else {\n if (typeof AbortSignal.any !== 'function') {\n throw new Error(\n 'AbortSignal.any is required for combining multiple signals. Please install a polyfill for environments that do not support it.'\n )\n }\n combinedSignal = AbortSignal.any(signals)\n controller = new AbortController()\n }\n const retryWithHooks = async () => {\n const effectiveRetries = init.retries ?? clientDefaultRetries\n const effectiveRetryDelay =\n typeof init.retryDelay !== 'undefined'\n ? init.retryDelay\n : clientDefaultRetryDelay\n const effectiveShouldRetry = init.shouldRetry ?? clientDefaultShouldRetry\n\n // Wrap shouldRetry to call onRetry hook\n let attempt = 0\n const shouldRetryWithHook = (ctx: import('./types').RetryContext) => {\n attempt = ctx.attempt\n const retrying = effectiveShouldRetry(ctx)\n if (retrying && attempt <= effectiveRetries) {\n effectiveHooks.onRetry?.(\n request,\n attempt - 1,\n ctx.error,\n ctx.response\n )\n }\n return retrying\n }\n\n let lastResponse: Response | undefined = undefined\n try {\n let res = await retry(\n async () => {\n // Check for aborts before making the request\n if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(request)\n // Throw AbortError for user aborts, no cause needed\n throw new AbortError('Request was aborted by user')\n }\n if (timeoutSignal?.aborted) {\n effectiveHooks.onTimeout?.(request)\n throw new TimeoutError('signal timed out')\n }\n if (typeof combinedSignal?.throwIfAborted === 'function') {\n combinedSignal.throwIfAborted()\n } else if (combinedSignal?.aborted) {\n if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(request)\n throw new AbortError('Request was aborted by user')\n } else if (timeoutSignal?.aborted) {\n effectiveHooks.onTimeout?.(request)\n throw new TimeoutError('signal timed out')\n } else {\n throw new AbortError(\n 'Request was aborted',\n new DOMException('Aborted', 'AbortError')\n )\n }\n }\n const reqWithSignal = new Request(request, {\n signal: combinedSignal,\n })\n try {\n const handler = fetchHandler ?? fetch\n const response = await handler(reqWithSignal)\n lastResponse = response\n if (\n breaker &&\n (response.status >= 500 || response.status === 429)\n ) {\n breaker.recordResult(response, undefined, request)\n }\n return response\n } catch (err) {\n if (breaker) breaker.recordResult(undefined, err, request)\n if (err instanceof DOMException && err.name === 'AbortError') {\n if (\n timeoutSignal?.aborted &&\n (!userSignal || !userSignal.aborted)\n ) {\n effectiveHooks.onTimeout?.(request)\n throw new TimeoutError('signal timed out', err)\n } else if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(request)\n throw new AbortError('Request was aborted by user')\n } else {\n throw new AbortError(\n 'Request was aborted',\n new DOMException('Aborted', 'AbortError')\n )\n }\n } else if (\n err instanceof TypeError &&\n /NetworkError|network error|failed to fetch|lost connection|NetworkError when attempting to fetch resource/i.test(\n err.message\n )\n ) {\n throw new NetworkError(err.message, err)\n }\n throw err\n }\n },\n effectiveRetries,\n effectiveRetryDelay,\n shouldRetryWithHook,\n request\n )\n if (effectiveHooks.transformResponse) {\n res = await effectiveHooks.transformResponse(res, request)\n }\n await effectiveHooks.after?.(request, res)\n await effectiveHooks.onComplete?.(request, res, undefined)\n // After all retries, if throwOnHttpError is true and final response is error, throw HttpError\n if (\n effectiveThrowOnHttpError &&\n ((res.status >= 400 && res.status < 500 && res.status !== 429) ||\n res.status >= 500 ||\n res.status === 429)\n ) {\n const { HttpError } = await import('./error.js')\n throw new HttpError(\n `HTTP error: ${res.status} ${res.statusText}`,\n res\n )\n }\n return res\n } catch (err: unknown) {\n // If lastResponse is set (any status), return or throw as needed\n if (lastResponse) {\n const resp = lastResponse as Response\n // Only throw if throwOnHttpError is true and final response is 4xx (not 429), 5xx, or 429\n if (\n effectiveThrowOnHttpError &&\n ((resp.status >= 400 && resp.status < 500 && resp.status !== 429) ||\n resp.status >= 500 ||\n resp.status === 429)\n ) {\n const { HttpError } = await import('./error.js')\n throw new HttpError(\n `HTTP error: ${resp.status} ${resp.statusText}`,\n resp\n )\n }\n return resp\n }\n // If the error is a known error type, re-throw it directly and call correct hook\n if (err instanceof TimeoutError) {\n await effectiveHooks.onTimeout?.(request)\n await effectiveHooks.onError?.(request, err)\n await effectiveHooks.onComplete?.(request, undefined, err)\n throw err\n }\n if (err instanceof AbortError) {\n await effectiveHooks.onAbort?.(request)\n await effectiveHooks.onError?.(request, err)\n await effectiveHooks.onComplete?.(request, undefined, err)\n throw err\n }\n if (err instanceof NetworkError) {\n await effectiveHooks.onError?.(request, err)\n await effectiveHooks.onComplete?.(request, undefined, err)\n throw err\n }\n // Otherwise, wrap in RetryLimitError\n const retryErr = new RetryLimitError(\n typeof err === 'object' &&\n err &&\n 'message' in err &&\n typeof (err as { message?: unknown }).message === 'string'\n ? (err as { message: string }).message\n : 'Retry limit reached',\n err\n )\n await effectiveHooks.onError?.(request, retryErr)\n await effectiveHooks.onComplete?.(request, undefined, retryErr)\n throw retryErr\n }\n }\n\n const promise = breaker\n ? breaker.invoke(retryWithHooks).catch(async (err: unknown) => {\n if (err instanceof CircuitOpenError) {\n await effectiveHooks.onCircuitOpen?.(request)\n await effectiveHooks.onError?.(request, err)\n await effectiveHooks.onComplete?.(request, undefined, err)\n } else {\n await effectiveHooks.onError?.(request, err)\n await effectiveHooks.onComplete?.(request, undefined, err)\n }\n throw err\n })\n : retryWithHooks()\n\n const entry: PendingRequest = {\n promise,\n request,\n controller,\n }\n pendingRequests.push(entry)\n\n return promise.finally(() => {\n const index = pendingRequests.indexOf(entry)\n if (index > -1) {\n pendingRequests.splice(index, 1)\n }\n })\n }\n\n // Add pendingRequests property to the client function (read-only)\n Object.defineProperty(client, 'pendingRequests', {\n get() {\n return pendingRequests\n },\n enumerable: false,\n configurable: false,\n })\n\n // Add abortAll method to the client function (read-only)\n Object.defineProperty(client, 'abortAll', {\n value: abortAll,\n writable: false,\n enumerable: false,\n configurable: false,\n })\n\n // Expose circuit breaker open state\n Object.defineProperty(client, 'circuitOpen', {\n get() {\n return breaker ? breaker.open : false\n },\n enumerable: true,\n })\n\n return client as FFetch\n}\n","export type { FFetch, FFetchOptions } from './types'\nexport type { Hooks } from './hooks'\n\nimport { createClient } from './client'\nexport { createClient } from './client'\n\nexport {\n TimeoutError,\n CircuitOpenError,\n AbortError,\n RetryLimitError,\n NetworkError,\n} from './error'\n\nexport default createClient\n"],"mappings":"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","names":["shouldRetry","controller"]}
|
package/dist/index.min.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var
|
|
1
|
+
import{a as y,b as g,c as h,d as v,e as x}from"./chunk-UP35S5ZH.min.js";var _=f=>{let o=f.response?.headers.get("Retry-After");if(o){let n=parseInt(o,10);if(!isNaN(n))return n*1e3;let u=Date.parse(o);if(!isNaN(u))return Math.max(0,u-Date.now())}return 2**f.attempt*200+Math.random()*100};async function z(f,o,n,u=()=>!0,T){let d,m;for(let R=0;R<=o;R++){let i={attempt:R+1,request:T,response:m,error:d};try{if(m=await f(),i.response=m,i.error=void 0,R<o&&u(i)){let c=typeof n=="function"?n(i):n;await new Promise(A=>setTimeout(A,c));continue}return m}catch(c){if(d=c,i.error=c,R===o||!u(i))throw c;let A=typeof n=="function"?n(i):n;await new Promise(C=>setTimeout(C,A))}}throw d}function B(f){let{error:o,response:n}=f;return o instanceof h||o instanceof g||o instanceof y?!1:n?n.status>=500||n.status===429:!0}var S=class{constructor(o,n){this.threshold=o;this.resetTimeout=n;this.failures=0;this.nextAttempt=0;this.isOpen=!1}get open(){return this.isOpen}recordResult(o,n,u){return n&&!(n instanceof v)?(this.setLastOpenRequest(u),this.onFailure(),!0):o&&(o.status>=500||o.status===429)?(this.setLastOpenRequest(u),this.onFailure(),!0):(u&&this.setLastSuccessRequest(u),this.onSuccess(),!1)}setHooks(o){this.hooks=o}setLastOpenRequest(o){this.lastOpenRequest=o}setLastSuccessRequest(o){this.lastSuccessRequest=o}async invoke(o){if(Date.now()<this.nextAttempt)throw new g("Circuit is open");try{let n=await o();return this.onSuccess(),n}catch(n){throw this.onFailure(),n}}onSuccess(){let o=this.isOpen;this.failures=0,o&&(this.isOpen=!1,this.hooks?.onCircuitClose&&this.lastSuccessRequest&&this.hooks.onCircuitClose(this.lastSuccessRequest)),this.lastSuccessRequest=void 0}onFailure(){this.failures++,this.failures>=this.threshold&&(this.nextAttempt=Date.now()+this.resetTimeout,this.isOpen=!0,this.hooks?.onCircuitOpen&&this.lastOpenRequest&&this.hooks.onCircuitOpen(this.lastOpenRequest))}};function j(f={}){let{timeout:o=5e3,retries:n=0,retryDelay:u=_,shouldRetry:T=B,hooks:d={},circuit:m,fetchHandler:R}=f,i=m?new S(m.threshold,m.reset):null;i&&(d.onCircuitClose||d.onCircuitOpen)&&i.setHooks({onCircuitClose:d.onCircuitClose,onCircuitOpen:d.onCircuitOpen});let c=[];function A(){for(let D of c)D.controller?.abort()}let C=async(D,l={})=>{let t=new Request(D,l),r={...d,...l.hooks||{}};r.transformRequest&&(t=await r.transformRequest(t)),await r.before?.(t);let I=typeof l.throwOnHttpError<"u"?l.throwOnHttpError:f.throwOnHttpError??!1;function G(a){if(typeof AbortSignal?.timeout=="function")return AbortSignal.timeout(a);let k=new AbortController,H=setTimeout(()=>k.abort(),a);return k.signal.addEventListener("abort",()=>clearTimeout(H),{once:!0}),k.signal}let M=l.timeout??o,w=l.signal,F=t.signal,q,E,P;M>0&&(q=G(M));let O=[];if(w&&O.push(w),F&&F!==w&&O.push(F),q&&O.push(q),O.length===1)E=O[0],P=new AbortController;else{if(typeof AbortSignal.any!="function")throw new Error("AbortSignal.any is required for combining multiple signals. Please install a polyfill for environments that do not support it.");E=AbortSignal.any(O),P=new AbortController}let $=async()=>{let a=l.retries??n,k=typeof l.retryDelay<"u"?l.retryDelay:u,H=l.shouldRetry??T,L=0,J=e=>{L=e.attempt;let p=H(e);return p&&L<=a&&r.onRetry?.(t,L-1,e.error,e.response),p},N;try{let e=await z(async()=>{if(w?.aborted)throw r.onAbort?.(t),new h("Request was aborted by user");if(q?.aborted)throw r.onTimeout?.(t),new y("signal timed out");if(typeof E?.throwIfAborted=="function")E.throwIfAborted();else if(E?.aborted)throw w?.aborted?(r.onAbort?.(t),new h("Request was aborted by user")):q?.aborted?(r.onTimeout?.(t),new y("signal timed out")):new h("Request was aborted",new DOMException("Aborted","AbortError"));let p=new Request(t,{signal:E});try{let b=await(R??fetch)(p);return N=b,i&&(b.status>=500||b.status===429)&&i.recordResult(b,void 0,t),b}catch(s){throw i&&i.recordResult(void 0,s,t),s instanceof DOMException&&s.name==="AbortError"?q?.aborted&&(!w||!w.aborted)?(r.onTimeout?.(t),new y("signal timed out",s)):w?.aborted?(r.onAbort?.(t),new h("Request was aborted by user")):new h("Request was aborted",new DOMException("Aborted","AbortError")):s instanceof TypeError&&/NetworkError|network error|failed to fetch|lost connection|NetworkError when attempting to fetch resource/i.test(s.message)?new x(s.message,s):s}},a,k,J,t);if(r.transformResponse&&(e=await r.transformResponse(e,t)),await r.after?.(t,e),await r.onComplete?.(t,e,void 0),I&&(e.status>=400&&e.status<500&&e.status!==429||e.status>=500||e.status===429)){let{HttpError:p}=await import("./error-7EEQP46E.min.js");throw new p(`HTTP error: ${e.status} ${e.statusText}`,e)}return e}catch(e){if(N){let s=N;if(I&&(s.status>=400&&s.status<500&&s.status!==429||s.status>=500||s.status===429)){let{HttpError:b}=await import("./error-7EEQP46E.min.js");throw new b(`HTTP error: ${s.status} ${s.statusText}`,s)}return s}if(e instanceof y)throw await r.onTimeout?.(t),await r.onError?.(t,e),await r.onComplete?.(t,void 0,e),e;if(e instanceof h)throw await r.onAbort?.(t),await r.onError?.(t,e),await r.onComplete?.(t,void 0,e),e;if(e instanceof x)throw await r.onError?.(t,e),await r.onComplete?.(t,void 0,e),e;let p=new v(typeof e=="object"&&e&&"message"in e&&typeof e.message=="string"?e.message:"Retry limit reached",e);throw await r.onError?.(t,p),await r.onComplete?.(t,void 0,p),p}},W=i?i.invoke($).catch(async a=>{throw a instanceof g?(await r.onCircuitOpen?.(t),await r.onError?.(t,a),await r.onComplete?.(t,void 0,a)):(await r.onError?.(t,a),await r.onComplete?.(t,void 0,a)),a}):$(),U={promise:W,request:t,controller:P};return c.push(U),W.finally(()=>{let a=c.indexOf(U);a>-1&&c.splice(a,1)})};return Object.defineProperty(C,"pendingRequests",{get(){return c},enumerable:!1,configurable:!1}),Object.defineProperty(C,"abortAll",{value:A,writable:!1,enumerable:!1,configurable:!1}),Object.defineProperty(C,"circuitOpen",{get(){return i?i.open:!1},enumerable:!0}),C}var se=j;export{h as AbortError,g as CircuitOpenError,x as NetworkError,v as RetryLimitError,y as TimeoutError,j as createClient,se as default};
|
|
2
2
|
//# sourceMappingURL=index.min.js.map
|
package/dist/index.min.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/retry.ts","../src/error.ts","../src/should-retry.ts","../src/circuit.ts","../src/client.ts","../src/index.ts"],"sourcesContent":["import type { RetryContext } from './types.js'\n\nexport type RetryDelay = number | ((ctx: RetryContext) => number)\n\nexport const defaultDelay: RetryDelay = (ctx) => {\n const retryAfter = ctx.response?.headers.get('Retry-After')\n if (retryAfter) {\n const seconds = parseInt(retryAfter, 10)\n if (!isNaN(seconds)) return seconds * 1000\n const date = Date.parse(retryAfter)\n if (!isNaN(date)) return Math.max(0, date - Date.now())\n }\n return 2 ** ctx.attempt * 200 + Math.random() * 100\n}\n\nexport async function retry(\n fn: () => Promise<Response>,\n retries: number,\n delay: RetryDelay,\n shouldRetry: (ctx: RetryContext) => boolean = () => true,\n request: Request\n): Promise<Response> {\n let lastErr: unknown\n let lastRes: Response | undefined\n\n for (let i = 0; i <= retries; i++) {\n const ctx: RetryContext = {\n attempt: i + 1,\n request,\n response: lastRes,\n error: lastErr,\n }\n try {\n lastRes = await fn()\n ctx.response = lastRes\n ctx.error = undefined\n if (i < retries && shouldRetry(ctx)) {\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await new Promise((r) => setTimeout(r, wait))\n continue\n }\n return lastRes\n } catch (err) {\n lastErr = err\n ctx.error = err\n if (i === retries || !shouldRetry(ctx)) throw err\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await new Promise((r) => setTimeout(r, wait))\n }\n }\n throw lastErr\n}\n","// Base error class to reduce duplication\nclass BaseError extends Error {\n public cause?: unknown\n constructor(name: string, message: string, cause?: unknown) {\n super(message)\n this.name = name\n if (cause !== undefined) this.cause = cause\n }\n}\n\nexport class TimeoutError extends BaseError {\n constructor(message = 'Request timed out', cause?: unknown) {\n super('TimeoutError', message, cause)\n }\n}\n\nexport class CircuitOpenError extends BaseError {\n constructor(message = 'Circuit is open', cause?: unknown) {\n super('CircuitOpenError', message, cause)\n }\n}\n\nexport class AbortError extends BaseError {\n constructor(message = 'Request was aborted', cause?: unknown) {\n super('AbortError', message, cause)\n }\n}\n\nexport class RetryLimitError extends BaseError {\n constructor(message = 'Retry limit reached', cause?: unknown) {\n super('RetryLimitError', message, cause)\n }\n}\n\nexport class NetworkError extends BaseError {\n constructor(message = 'Network error occurred', cause?: unknown) {\n super('NetworkError', message, cause)\n }\n}\n","import { AbortError, CircuitOpenError, TimeoutError } from './error.js'\nimport type { RetryContext } from './types.js'\n\nexport function shouldRetry(ctx: RetryContext): boolean {\n const { error, response } = ctx\n if (\n error instanceof AbortError ||\n error instanceof CircuitOpenError ||\n error instanceof TimeoutError\n )\n return false\n if (!response) return true // network error\n return response.status >= 500 || response.status === 429\n}\n","import { CircuitOpenError, RetryLimitError } from './error.js'\n\nexport class CircuitBreaker {\n private failures = 0\n private nextAttempt = 0\n private isOpen = false\n\n // Returns true if the circuit breaker is currently open (blocking requests).\n get open(): boolean {\n return this.isOpen\n }\n private hooks?: {\n onCircuitOpen?: (req: Request) => void | Promise<void>\n onCircuitClose?: (req: Request) => void | Promise<void>\n }\n private lastSuccessRequest?: Request\n private lastOpenRequest?: Request\n\n constructor(\n private threshold: number,\n private resetTimeout: number\n ) {}\n\n // Call this after each request to record the result and update circuit state.\n // Returns true if the result was counted as a failure.\n recordResult(response?: Response, error?: unknown, req?: Request): boolean {\n // Count thrown errors (network, abort, timeout) as failures\n if (error && !(error instanceof RetryLimitError)) {\n this.setLastOpenRequest(req!)\n this.onFailure()\n return true\n }\n // Count HTTP 5xx and 429 responses as failures\n if (response && (response.status >= 500 || response.status === 429)) {\n this.setLastOpenRequest(req!)\n this.onFailure()\n return true\n }\n // Otherwise, count as success\n if (req) this.setLastSuccessRequest(req)\n this.onSuccess()\n return false\n }\n\n setHooks(hooks: {\n onCircuitOpen?: (req: Request) => void | Promise<void>\n onCircuitClose?: (req: Request) => void | Promise<void>\n }) {\n this.hooks = hooks\n }\n setLastOpenRequest(req: Request) {\n this.lastOpenRequest = req\n }\n\n setLastSuccessRequest(req: Request) {\n this.lastSuccessRequest = req\n }\n\n async invoke<T>(fn: () => Promise<T>): Promise<T> {\n if (Date.now() < this.nextAttempt)\n throw new CircuitOpenError('Circuit is open')\n try {\n const res = await fn()\n this.onSuccess()\n return res\n } catch (err) {\n this.onFailure()\n throw err\n }\n }\n\n private onSuccess() {\n const wasOpen = this.isOpen\n this.failures = 0\n if (wasOpen) {\n this.isOpen = false\n if (this.hooks?.onCircuitClose && this.lastSuccessRequest) {\n this.hooks.onCircuitClose(this.lastSuccessRequest)\n }\n }\n this.lastSuccessRequest = undefined\n }\n\n private onFailure() {\n this.failures++\n if (this.failures >= this.threshold) {\n this.nextAttempt = Date.now() + this.resetTimeout\n this.isOpen = true\n if (this.hooks?.onCircuitOpen && this.lastOpenRequest) {\n this.hooks.onCircuitOpen(this.lastOpenRequest)\n }\n }\n }\n}\n","import type {\n FFetchOptions,\n FFetch,\n FFetchRequestInit,\n PendingRequest,\n} from './types.js'\nimport { retry, defaultDelay } from './retry.js'\nimport { shouldRetry as defaultShouldRetry } from './should-retry.js'\nimport { CircuitBreaker } from './circuit.js'\nimport {\n TimeoutError,\n CircuitOpenError,\n AbortError,\n RetryLimitError,\n NetworkError,\n} from './error.js'\n\nexport function createClient(opts: FFetchOptions = {}): FFetch {\n const {\n timeout: clientDefaultTimeout = 5_000,\n retries: clientDefaultRetries = 0,\n retryDelay: clientDefaultRetryDelay = defaultDelay,\n shouldRetry: clientDefaultShouldRetry = defaultShouldRetry,\n hooks: clientDefaultHooks = {},\n circuit: clientDefaultCircuit,\n fetchHandler,\n } = opts\n\n const breaker = clientDefaultCircuit\n ? new CircuitBreaker(\n clientDefaultCircuit.threshold,\n clientDefaultCircuit.reset\n )\n : null\n\n if (\n breaker &&\n (clientDefaultHooks.onCircuitClose || clientDefaultHooks.onCircuitOpen)\n ) {\n breaker.setHooks({\n onCircuitClose: clientDefaultHooks.onCircuitClose,\n onCircuitOpen: clientDefaultHooks.onCircuitOpen,\n })\n }\n\n const pendingRequests: PendingRequest[] = []\n\n // Helper to abort all pending requests\n function abortAll() {\n for (const entry of pendingRequests) {\n entry.controller?.abort()\n }\n }\n\n const client = async (\n input: RequestInfo | URL,\n init: FFetchRequestInit = {}\n ) => {\n // No longer require AbortSignal.timeout - we'll implement it manually if needed\n let request = new Request(input, init)\n\n // Merge hooks: per-request hooks override client hooks, but fallback to client hooks\n const effectiveHooks = { ...clientDefaultHooks, ...(init.hooks || {}) }\n if (effectiveHooks.transformRequest) {\n request = await effectiveHooks.transformRequest(request)\n }\n await effectiveHooks.before?.(request)\n\n // Create timeout signal (manual implementation if AbortSignal.timeout not available)\n function createTimeoutSignal(timeout: number): AbortSignal {\n if (typeof AbortSignal?.timeout === 'function') {\n return AbortSignal.timeout(timeout)\n }\n\n // Manual implementation for older environments\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n\n // Clean up timeout if signal is aborted early by other means\n controller.signal.addEventListener(\n 'abort',\n () => clearTimeout(timeoutId),\n { once: true }\n )\n\n return controller.signal\n }\n\n // AbortSignal.timeout/any logic ---\n const effectiveTimeout = init.timeout ?? clientDefaultTimeout\n const userSignal = init.signal\n const transformedSignal = request.signal // Extract signal from transformed request\n let timeoutSignal: AbortSignal | undefined = undefined\n let combinedSignal: AbortSignal | undefined = undefined\n let controller: AbortController | undefined = undefined\n\n if (effectiveTimeout > 0) {\n timeoutSignal = createTimeoutSignal(effectiveTimeout)\n }\n\n // Collect all signals that need to be combined\n const signals: AbortSignal[] = []\n if (userSignal) signals.push(userSignal)\n if (transformedSignal && transformedSignal !== userSignal) {\n signals.push(transformedSignal)\n }\n if (timeoutSignal) signals.push(timeoutSignal)\n\n // Use AbortSignal.any for signal combination. Requires native support or a polyfill.\n // If not available, instruct users to install a polyfill for environments lacking AbortSignal.any.\n // there are always 1 or more signals\n if (signals.length === 1) {\n combinedSignal = signals[0]\n controller = new AbortController()\n } else {\n if (typeof AbortSignal.any !== 'function') {\n throw new Error(\n 'AbortSignal.any is required for combining multiple signals. Please install a polyfill for environments that do not support it.'\n )\n }\n combinedSignal = AbortSignal.any(signals)\n controller = new AbortController()\n }\n const retryWithHooks = async () => {\n const effectiveRetries = init.retries ?? clientDefaultRetries\n const effectiveRetryDelay =\n typeof init.retryDelay !== 'undefined'\n ? init.retryDelay\n : clientDefaultRetryDelay\n const effectiveShouldRetry = init.shouldRetry ?? clientDefaultShouldRetry\n\n // Wrap shouldRetry to call onRetry hook\n let attempt = 0\n const shouldRetryWithHook = (ctx: import('./types').RetryContext) => {\n attempt = ctx.attempt\n const retrying = effectiveShouldRetry(ctx)\n if (retrying && attempt <= effectiveRetries) {\n effectiveHooks.onRetry?.(\n request,\n attempt - 1,\n ctx.error,\n ctx.response\n )\n }\n return retrying\n }\n\n function mapToCustomError(err: unknown): unknown {\n if (err instanceof DOMException && err.name === 'AbortError') {\n if (timeoutSignal?.aborted && (!userSignal || !userSignal.aborted)) {\n return new TimeoutError('signal timed out', err)\n } else {\n return new AbortError('Request was aborted', err)\n }\n } else if (\n err instanceof TypeError &&\n /NetworkError|network error|failed to fetch|lost connection|NetworkError when attempting to fetch resource/i.test(\n err.message\n )\n ) {\n return new NetworkError(err.message, err)\n }\n return err\n }\n\n async function handleError(err: unknown): Promise<never> {\n err = mapToCustomError(err)\n // If user aborted, always throw AbortError, not RetryLimitError\n if (userSignal?.aborted) {\n const abortErr = new AbortError('Request was aborted by user')\n await effectiveHooks.onAbort?.(request)\n await effectiveHooks.onError?.(request, abortErr)\n await effectiveHooks.onComplete?.(request, undefined, abortErr)\n throw abortErr\n }\n // If the error is a custom error, re-throw it directly (do not wrap)\n if (\n err instanceof TimeoutError ||\n err instanceof NetworkError ||\n err instanceof AbortError\n ) {\n if (err instanceof TimeoutError) {\n await effectiveHooks.onTimeout?.(request)\n }\n if (err instanceof AbortError) {\n await effectiveHooks.onAbort?.(request)\n }\n await effectiveHooks.onError?.(request, err)\n await effectiveHooks.onComplete?.(request, undefined, err)\n throw err\n }\n // Otherwise, throw RetryLimitError after all retries are exhausted\n const retryErr = new RetryLimitError(\n typeof err === 'object' &&\n err &&\n 'message' in err &&\n typeof (err as { message?: unknown }).message === 'string'\n ? (err as { message: string }).message\n : 'Retry limit reached'\n )\n await effectiveHooks.onError?.(request, retryErr)\n await effectiveHooks.onComplete?.(request, undefined, retryErr)\n throw retryErr\n }\n\n try {\n let res = await retry(\n async () => {\n // Use AbortSignal.throwIfAborted() before starting fetch\n if (typeof combinedSignal?.throwIfAborted === 'function') {\n combinedSignal.throwIfAborted()\n } else if (combinedSignal?.aborted) {\n throw new AbortError('Request was aborted')\n }\n const reqWithSignal = new Request(request, {\n signal: combinedSignal,\n })\n try {\n const handler = fetchHandler ?? fetch\n const response = await handler(reqWithSignal)\n // Circuit breaker: record result\n if (breaker) {\n if (breaker.recordResult(response, undefined, request)) {\n throw new Error(`HTTP error: ${response.status}`)\n }\n }\n return response\n } catch (err) {\n if (breaker) breaker.recordResult(undefined, err, request)\n throw mapToCustomError(err)\n }\n },\n effectiveRetries,\n effectiveRetryDelay,\n shouldRetryWithHook,\n request\n )\n if (effectiveHooks.transformResponse) {\n res = await effectiveHooks.transformResponse(res, request)\n }\n await effectiveHooks.after?.(request, res)\n await effectiveHooks.onComplete?.(request, res, undefined)\n return res\n } catch (err: unknown) {\n await handleError(err)\n throw new Error('Unreachable: handleError should always throw')\n }\n }\n\n const promise = breaker\n ? breaker.invoke(retryWithHooks).catch(async (err: unknown) => {\n if (err instanceof CircuitOpenError) {\n await effectiveHooks.onCircuitOpen?.(request)\n await effectiveHooks.onError?.(request, err)\n await effectiveHooks.onComplete?.(request, undefined, err)\n } else {\n await effectiveHooks.onError?.(request, err)\n await effectiveHooks.onComplete?.(request, undefined, err)\n }\n throw err\n })\n : retryWithHooks()\n\n const entry: PendingRequest = {\n promise,\n request,\n controller,\n }\n pendingRequests.push(entry)\n\n return promise.finally(() => {\n const index = pendingRequests.indexOf(entry)\n if (index > -1) {\n pendingRequests.splice(index, 1)\n }\n })\n }\n\n // Add pendingRequests property to the client function (read-only)\n Object.defineProperty(client, 'pendingRequests', {\n get() {\n return pendingRequests\n },\n enumerable: false,\n configurable: false,\n })\n\n // Add abortAll method to the client function (read-only)\n Object.defineProperty(client, 'abortAll', {\n value: abortAll,\n writable: false,\n enumerable: false,\n configurable: false,\n })\n\n // Expose circuit breaker open state\n Object.defineProperty(client, 'circuitOpen', {\n get() {\n return breaker ? breaker.open : false\n },\n enumerable: true,\n })\n\n return client as FFetch\n}\n","export type { FFetch, FFetchOptions } from './types'\nexport type { Hooks } from './hooks'\n\nimport { createClient } from './client'\nexport { createClient } from './client'\n\nexport {\n TimeoutError,\n CircuitOpenError,\n AbortError,\n RetryLimitError,\n NetworkError,\n} from './error'\n\nexport default createClient\n"],"mappings":"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","names":["defaultDelay","ctx","retryAfter","seconds","date","retry","fn","retries","delay","shouldRetry","request","lastErr","lastRes","i","wait","r","err","BaseError","name","message","cause","TimeoutError","CircuitOpenError","AbortError","RetryLimitError","NetworkError","shouldRetry","ctx","error","response","AbortError","CircuitOpenError","TimeoutError","CircuitBreaker","threshold","resetTimeout","response","error","req","RetryLimitError","hooks","fn","CircuitOpenError","res","err","wasOpen","createClient","opts","clientDefaultTimeout","clientDefaultRetries","clientDefaultRetryDelay","defaultDelay","clientDefaultShouldRetry","shouldRetry","clientDefaultHooks","clientDefaultCircuit","fetchHandler","breaker","CircuitBreaker","pendingRequests","abortAll","entry","client","input","init","request","effectiveHooks","createTimeoutSignal","timeout","controller","timeoutId","effectiveTimeout","userSignal","transformedSignal","timeoutSignal","combinedSignal","signals","retryWithHooks","effectiveRetries","effectiveRetryDelay","effectiveShouldRetry","attempt","shouldRetryWithHook","ctx","retrying","mapToCustomError","err","TimeoutError","AbortError","NetworkError","handleError","abortErr","retryErr","RetryLimitError","res","retry","reqWithSignal","response","promise","CircuitOpenError","index","index_default","createClient"]}
|
|
1
|
+
{"version":3,"sources":["../src/retry.ts","../src/should-retry.ts","../src/circuit.ts","../src/client.ts","../src/index.ts"],"sourcesContent":["import type { RetryContext } from './types.js'\n\nexport type RetryDelay = number | ((ctx: RetryContext) => number)\n\nexport const defaultDelay: RetryDelay = (ctx) => {\n const retryAfter = ctx.response?.headers.get('Retry-After')\n if (retryAfter) {\n const seconds = parseInt(retryAfter, 10)\n if (!isNaN(seconds)) return seconds * 1000\n const date = Date.parse(retryAfter)\n if (!isNaN(date)) return Math.max(0, date - Date.now())\n }\n return 2 ** ctx.attempt * 200 + Math.random() * 100\n}\n\nexport async function retry(\n fn: () => Promise<Response>,\n retries: number,\n delay: RetryDelay,\n shouldRetry: (ctx: RetryContext) => boolean = () => true,\n request: Request\n): Promise<Response> {\n let lastErr: unknown\n let lastRes: Response | undefined\n\n for (let i = 0; i <= retries; i++) {\n const ctx: RetryContext = {\n attempt: i + 1,\n request,\n response: lastRes,\n error: lastErr,\n }\n try {\n lastRes = await fn()\n ctx.response = lastRes\n ctx.error = undefined\n if (i < retries && shouldRetry(ctx)) {\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await new Promise((r) => setTimeout(r, wait))\n continue\n }\n return lastRes\n } catch (err) {\n lastErr = err\n ctx.error = err\n if (i === retries || !shouldRetry(ctx)) throw err\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await new Promise((r) => setTimeout(r, wait))\n }\n }\n throw lastErr\n}\n","import { AbortError, CircuitOpenError, TimeoutError } from './error.js'\nimport type { RetryContext } from './types.js'\n\nexport function shouldRetry(ctx: RetryContext): boolean {\n const { error, response } = ctx\n if (\n error instanceof AbortError ||\n error instanceof CircuitOpenError ||\n error instanceof TimeoutError\n )\n return false\n if (!response) return true // network error\n return response.status >= 500 || response.status === 429\n}\n","import { CircuitOpenError, RetryLimitError } from './error.js'\n\nexport class CircuitBreaker {\n private failures = 0\n private nextAttempt = 0\n private isOpen = false\n\n // Returns true if the circuit breaker is currently open (blocking requests).\n get open(): boolean {\n return this.isOpen\n }\n private hooks?: {\n onCircuitOpen?: (req: Request) => void | Promise<void>\n onCircuitClose?: (req: Request) => void | Promise<void>\n }\n private lastSuccessRequest?: Request\n private lastOpenRequest?: Request\n\n constructor(\n private threshold: number,\n private resetTimeout: number\n ) {}\n\n // Call this after each request to record the result and update circuit state.\n // Returns true if the result was counted as a failure.\n recordResult(response?: Response, error?: unknown, req?: Request): boolean {\n // Count thrown errors (network, abort, timeout) as failures\n if (error && !(error instanceof RetryLimitError)) {\n this.setLastOpenRequest(req!)\n this.onFailure()\n return true\n }\n // Count HTTP 5xx and 429 responses as failures\n if (response && (response.status >= 500 || response.status === 429)) {\n this.setLastOpenRequest(req!)\n this.onFailure()\n return true\n }\n // Otherwise, count as success\n if (req) this.setLastSuccessRequest(req)\n this.onSuccess()\n return false\n }\n\n setHooks(hooks: {\n onCircuitOpen?: (req: Request) => void | Promise<void>\n onCircuitClose?: (req: Request) => void | Promise<void>\n }) {\n this.hooks = hooks\n }\n setLastOpenRequest(req: Request) {\n this.lastOpenRequest = req\n }\n\n setLastSuccessRequest(req: Request) {\n this.lastSuccessRequest = req\n }\n\n async invoke<T>(fn: () => Promise<T>): Promise<T> {\n if (Date.now() < this.nextAttempt)\n throw new CircuitOpenError('Circuit is open')\n try {\n const res = await fn()\n this.onSuccess()\n return res\n } catch (err) {\n this.onFailure()\n throw err\n }\n }\n\n private onSuccess() {\n const wasOpen = this.isOpen\n this.failures = 0\n if (wasOpen) {\n this.isOpen = false\n if (this.hooks?.onCircuitClose && this.lastSuccessRequest) {\n this.hooks.onCircuitClose(this.lastSuccessRequest)\n }\n }\n this.lastSuccessRequest = undefined\n }\n\n private onFailure() {\n this.failures++\n if (this.failures >= this.threshold) {\n this.nextAttempt = Date.now() + this.resetTimeout\n this.isOpen = true\n if (this.hooks?.onCircuitOpen && this.lastOpenRequest) {\n this.hooks.onCircuitOpen(this.lastOpenRequest)\n }\n }\n }\n}\n","import type {\n FFetchOptions,\n FFetch,\n FFetchRequestInit,\n PendingRequest,\n} from './types.js'\nimport { retry, defaultDelay } from './retry.js'\nimport { shouldRetry as defaultShouldRetry } from './should-retry.js'\nimport { CircuitBreaker } from './circuit.js'\nimport {\n TimeoutError,\n CircuitOpenError,\n AbortError,\n RetryLimitError,\n NetworkError,\n} from './error.js'\n\nexport function createClient(opts: FFetchOptions = {}): FFetch {\n const {\n timeout: clientDefaultTimeout = 5_000,\n retries: clientDefaultRetries = 0,\n retryDelay: clientDefaultRetryDelay = defaultDelay,\n shouldRetry: clientDefaultShouldRetry = defaultShouldRetry,\n hooks: clientDefaultHooks = {},\n circuit: clientDefaultCircuit,\n fetchHandler,\n } = opts\n\n const breaker = clientDefaultCircuit\n ? new CircuitBreaker(\n clientDefaultCircuit.threshold,\n clientDefaultCircuit.reset\n )\n : null\n\n if (\n breaker &&\n (clientDefaultHooks.onCircuitClose || clientDefaultHooks.onCircuitOpen)\n ) {\n breaker.setHooks({\n onCircuitClose: clientDefaultHooks.onCircuitClose,\n onCircuitOpen: clientDefaultHooks.onCircuitOpen,\n })\n }\n\n const pendingRequests: PendingRequest[] = []\n\n // Helper to abort all pending requests\n function abortAll() {\n for (const entry of pendingRequests) {\n entry.controller?.abort()\n }\n }\n\n const client = async (\n input: RequestInfo | URL,\n init: FFetchRequestInit = {}\n ) => {\n // No longer require AbortSignal.timeout - we'll implement it manually if needed\n let request = new Request(input, init)\n\n // Merge hooks: per-request hooks override client hooks, but fallback to client hooks\n const effectiveHooks = { ...clientDefaultHooks, ...(init.hooks || {}) }\n if (effectiveHooks.transformRequest) {\n request = await effectiveHooks.transformRequest(request)\n }\n await effectiveHooks.before?.(request)\n\n // Determine throwOnHttpError (per-request overrides client default)\n const effectiveThrowOnHttpError =\n typeof init.throwOnHttpError !== 'undefined'\n ? init.throwOnHttpError\n : (opts.throwOnHttpError ?? false)\n\n // Create timeout signal (manual implementation if AbortSignal.timeout not available)\n function createTimeoutSignal(timeout: number): AbortSignal {\n if (typeof AbortSignal?.timeout === 'function') {\n return AbortSignal.timeout(timeout)\n }\n\n // Manual implementation for older environments\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n\n // Clean up timeout if signal is aborted early by other means\n controller.signal.addEventListener(\n 'abort',\n () => clearTimeout(timeoutId),\n { once: true }\n )\n\n return controller.signal\n }\n\n // AbortSignal.timeout/any logic ---\n const effectiveTimeout = init.timeout ?? clientDefaultTimeout\n const userSignal = init.signal\n const transformedSignal = request.signal // Extract signal from transformed request\n let timeoutSignal: AbortSignal | undefined = undefined\n let combinedSignal: AbortSignal | undefined = undefined\n let controller: AbortController | undefined = undefined\n\n if (effectiveTimeout > 0) {\n timeoutSignal = createTimeoutSignal(effectiveTimeout)\n }\n\n // Collect all signals that need to be combined\n const signals: AbortSignal[] = []\n if (userSignal) signals.push(userSignal)\n if (transformedSignal && transformedSignal !== userSignal) {\n signals.push(transformedSignal)\n }\n if (timeoutSignal) signals.push(timeoutSignal)\n\n // Use AbortSignal.any for signal combination. Requires native support or a polyfill.\n // If not available, instruct users to install a polyfill for environments lacking AbortSignal.any.\n // there are always 1 or more signals\n if (signals.length === 1) {\n combinedSignal = signals[0]\n controller = new AbortController()\n } else {\n if (typeof AbortSignal.any !== 'function') {\n throw new Error(\n 'AbortSignal.any is required for combining multiple signals. Please install a polyfill for environments that do not support it.'\n )\n }\n combinedSignal = AbortSignal.any(signals)\n controller = new AbortController()\n }\n const retryWithHooks = async () => {\n const effectiveRetries = init.retries ?? clientDefaultRetries\n const effectiveRetryDelay =\n typeof init.retryDelay !== 'undefined'\n ? init.retryDelay\n : clientDefaultRetryDelay\n const effectiveShouldRetry = init.shouldRetry ?? clientDefaultShouldRetry\n\n // Wrap shouldRetry to call onRetry hook\n let attempt = 0\n const shouldRetryWithHook = (ctx: import('./types').RetryContext) => {\n attempt = ctx.attempt\n const retrying = effectiveShouldRetry(ctx)\n if (retrying && attempt <= effectiveRetries) {\n effectiveHooks.onRetry?.(\n request,\n attempt - 1,\n ctx.error,\n ctx.response\n )\n }\n return retrying\n }\n\n let lastResponse: Response | undefined = undefined\n try {\n let res = await retry(\n async () => {\n // Check for aborts before making the request\n if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(request)\n // Throw AbortError for user aborts, no cause needed\n throw new AbortError('Request was aborted by user')\n }\n if (timeoutSignal?.aborted) {\n effectiveHooks.onTimeout?.(request)\n throw new TimeoutError('signal timed out')\n }\n if (typeof combinedSignal?.throwIfAborted === 'function') {\n combinedSignal.throwIfAborted()\n } else if (combinedSignal?.aborted) {\n if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(request)\n throw new AbortError('Request was aborted by user')\n } else if (timeoutSignal?.aborted) {\n effectiveHooks.onTimeout?.(request)\n throw new TimeoutError('signal timed out')\n } else {\n throw new AbortError(\n 'Request was aborted',\n new DOMException('Aborted', 'AbortError')\n )\n }\n }\n const reqWithSignal = new Request(request, {\n signal: combinedSignal,\n })\n try {\n const handler = fetchHandler ?? fetch\n const response = await handler(reqWithSignal)\n lastResponse = response\n if (\n breaker &&\n (response.status >= 500 || response.status === 429)\n ) {\n breaker.recordResult(response, undefined, request)\n }\n return response\n } catch (err) {\n if (breaker) breaker.recordResult(undefined, err, request)\n if (err instanceof DOMException && err.name === 'AbortError') {\n if (\n timeoutSignal?.aborted &&\n (!userSignal || !userSignal.aborted)\n ) {\n effectiveHooks.onTimeout?.(request)\n throw new TimeoutError('signal timed out', err)\n } else if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(request)\n throw new AbortError('Request was aborted by user')\n } else {\n throw new AbortError(\n 'Request was aborted',\n new DOMException('Aborted', 'AbortError')\n )\n }\n } else if (\n err instanceof TypeError &&\n /NetworkError|network error|failed to fetch|lost connection|NetworkError when attempting to fetch resource/i.test(\n err.message\n )\n ) {\n throw new NetworkError(err.message, err)\n }\n throw err\n }\n },\n effectiveRetries,\n effectiveRetryDelay,\n shouldRetryWithHook,\n request\n )\n if (effectiveHooks.transformResponse) {\n res = await effectiveHooks.transformResponse(res, request)\n }\n await effectiveHooks.after?.(request, res)\n await effectiveHooks.onComplete?.(request, res, undefined)\n // After all retries, if throwOnHttpError is true and final response is error, throw HttpError\n if (\n effectiveThrowOnHttpError &&\n ((res.status >= 400 && res.status < 500 && res.status !== 429) ||\n res.status >= 500 ||\n res.status === 429)\n ) {\n const { HttpError } = await import('./error.js')\n throw new HttpError(\n `HTTP error: ${res.status} ${res.statusText}`,\n res\n )\n }\n return res\n } catch (err: unknown) {\n // If lastResponse is set (any status), return or throw as needed\n if (lastResponse) {\n const resp = lastResponse as Response\n // Only throw if throwOnHttpError is true and final response is 4xx (not 429), 5xx, or 429\n if (\n effectiveThrowOnHttpError &&\n ((resp.status >= 400 && resp.status < 500 && resp.status !== 429) ||\n resp.status >= 500 ||\n resp.status === 429)\n ) {\n const { HttpError } = await import('./error.js')\n throw new HttpError(\n `HTTP error: ${resp.status} ${resp.statusText}`,\n resp\n )\n }\n return resp\n }\n // If the error is a known error type, re-throw it directly and call correct hook\n if (err instanceof TimeoutError) {\n await effectiveHooks.onTimeout?.(request)\n await effectiveHooks.onError?.(request, err)\n await effectiveHooks.onComplete?.(request, undefined, err)\n throw err\n }\n if (err instanceof AbortError) {\n await effectiveHooks.onAbort?.(request)\n await effectiveHooks.onError?.(request, err)\n await effectiveHooks.onComplete?.(request, undefined, err)\n throw err\n }\n if (err instanceof NetworkError) {\n await effectiveHooks.onError?.(request, err)\n await effectiveHooks.onComplete?.(request, undefined, err)\n throw err\n }\n // Otherwise, wrap in RetryLimitError\n const retryErr = new RetryLimitError(\n typeof err === 'object' &&\n err &&\n 'message' in err &&\n typeof (err as { message?: unknown }).message === 'string'\n ? (err as { message: string }).message\n : 'Retry limit reached',\n err\n )\n await effectiveHooks.onError?.(request, retryErr)\n await effectiveHooks.onComplete?.(request, undefined, retryErr)\n throw retryErr\n }\n }\n\n const promise = breaker\n ? breaker.invoke(retryWithHooks).catch(async (err: unknown) => {\n if (err instanceof CircuitOpenError) {\n await effectiveHooks.onCircuitOpen?.(request)\n await effectiveHooks.onError?.(request, err)\n await effectiveHooks.onComplete?.(request, undefined, err)\n } else {\n await effectiveHooks.onError?.(request, err)\n await effectiveHooks.onComplete?.(request, undefined, err)\n }\n throw err\n })\n : retryWithHooks()\n\n const entry: PendingRequest = {\n promise,\n request,\n controller,\n }\n pendingRequests.push(entry)\n\n return promise.finally(() => {\n const index = pendingRequests.indexOf(entry)\n if (index > -1) {\n pendingRequests.splice(index, 1)\n }\n })\n }\n\n // Add pendingRequests property to the client function (read-only)\n Object.defineProperty(client, 'pendingRequests', {\n get() {\n return pendingRequests\n },\n enumerable: false,\n configurable: false,\n })\n\n // Add abortAll method to the client function (read-only)\n Object.defineProperty(client, 'abortAll', {\n value: abortAll,\n writable: false,\n enumerable: false,\n configurable: false,\n })\n\n // Expose circuit breaker open state\n Object.defineProperty(client, 'circuitOpen', {\n get() {\n return breaker ? breaker.open : false\n },\n enumerable: true,\n })\n\n return client as FFetch\n}\n","export type { FFetch, FFetchOptions } from './types'\nexport type { Hooks } from './hooks'\n\nimport { createClient } from './client'\nexport { createClient } from './client'\n\nexport {\n TimeoutError,\n CircuitOpenError,\n AbortError,\n RetryLimitError,\n NetworkError,\n} from './error'\n\nexport default createClient\n"],"mappings":"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","names":["defaultDelay","ctx","retryAfter","seconds","date","retry","fn","retries","delay","shouldRetry","request","lastErr","lastRes","i","wait","r","err","shouldRetry","ctx","error","response","AbortError","CircuitOpenError","TimeoutError","CircuitBreaker","threshold","resetTimeout","response","error","req","RetryLimitError","hooks","fn","CircuitOpenError","res","err","wasOpen","createClient","opts","clientDefaultTimeout","clientDefaultRetries","clientDefaultRetryDelay","defaultDelay","clientDefaultShouldRetry","shouldRetry","clientDefaultHooks","clientDefaultCircuit","fetchHandler","breaker","CircuitBreaker","pendingRequests","abortAll","entry","client","input","init","request","effectiveHooks","effectiveThrowOnHttpError","createTimeoutSignal","timeout","controller","timeoutId","effectiveTimeout","userSignal","transformedSignal","timeoutSignal","combinedSignal","signals","retryWithHooks","effectiveRetries","effectiveRetryDelay","effectiveShouldRetry","attempt","shouldRetryWithHook","ctx","retrying","lastResponse","res","retry","AbortError","TimeoutError","reqWithSignal","response","err","NetworkError","HttpError","resp","retryErr","RetryLimitError","promise","CircuitOpenError","index","index_default","createClient"]}
|