@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 CHANGED
@@ -1,15 +1,15 @@
1
- ![npm](https://img.shields.io/npm/v/@gkoos/ffetch)
2
- ![Downloads](https://img.shields.io/npm/dm/@gkoos/ffetch)
3
- ![GitHub stars](https://img.shields.io/github/stars/gkoos/ffetch?style=social)
1
+ ![npm](https://img.shields.io/npm/v/@fetchkit/ffetch)
2
+ ![Downloads](https://img.shields.io/npm/dm/@fetchkit/ffetch)
3
+ ![GitHub stars](https://img.shields.io/github/stars/fetch-kit/ffetch?style=social)
4
4
 
5
- ![Build](https://github.com/gkoos/ffetch/actions/workflows/ci.yml/badge.svg)
6
- ![codecov](https://codecov.io/gh/gkoos/ffetch/branch/main/graph/badge.svg)
5
+ ![Build](https://github.com/fetch-kit/ffetch/actions/workflows/ci.yml/badge.svg)
6
+ ![codecov](https://codecov.io/gh/fetch-kit/ffetch/branch/main/graph/badge.svg)
7
7
 
8
- ![MIT](https://img.shields.io/npm/l/@gkoos/ffetch)
9
- ![bundlephobia](https://badgen.net/bundlephobia/minzip/@gkoos/ffetch)
10
- ![Types](https://img.shields.io/npm/types/@gkoos/ffetch)
8
+ ![MIT](https://img.shields.io/npm/l/@fetchkit/ffetch)
9
+ ![bundlephobia](https://badgen.net/bundlephobia/minzip/@fetchkit/ffetch)
10
+ ![Types](https://img.shields.io/npm/types/@fetchkit/ffetch)
11
11
 
12
- # @gkoos/ffetch
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 @gkoos/ffetch
35
+ npm install @fetchkit/ffetch
35
36
  ```
36
37
 
37
38
  ### Basic Usage
38
39
 
39
40
  ```typescript
40
- import createClient from '@gkoos/ffetch'
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 '@gkoos/ffetch'
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/@gkoos/ffetch/dist/index.min.js'
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,2 @@
1
+ import{a,b,c,d,e,f}from"./chunk-UP35S5ZH.min.js";export{c as AbortError,b as CircuitOpenError,f as HttpError,e as NetworkError,d as RetryLimitError,a as TimeoutError};
2
+ //# sourceMappingURL=error-7EEQP46E.min.js.map
@@ -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
- function mapToCustomError(err) {
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
- throw new AbortError("Request was aborted");
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
- if (breaker) {
340
- if (breaker.recordResult(response, void 0, request)) {
341
- throw new Error(`HTTP error: ${response.status}`);
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
- throw mapToCustomError(err);
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
- await handleError(err);
363
- throw new Error("Unreachable: handleError should always throw");
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 = {
@@ -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
@@ -23,6 +23,7 @@ interface FFetchOptions {
23
23
  retries?: number;
24
24
  retryDelay?: number | ((ctx: RetryContext) => number);
25
25
  shouldRetry?: (ctx: RetryContext) => boolean;
26
+ throwOnHttpError?: boolean;
26
27
  circuit?: {
27
28
  threshold: number;
28
29
  reset: number;
package/dist/index.d.ts CHANGED
@@ -23,6 +23,7 @@ interface FFetchOptions {
23
23
  retries?: number;
24
24
  retryDelay?: number | ((ctx: RetryContext) => number);
25
25
  shouldRetry?: (ctx: RetryContext) => boolean;
26
+ throwOnHttpError?: boolean;
26
27
  circuit?: {
27
28
  threshold: number;
28
29
  reset: number;
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
- function mapToCustomError(err) {
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
- throw new AbortError("Request was aborted");
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
- if (breaker) {
308
- if (breaker.recordResult(response, void 0, request)) {
309
- throw new Error(`HTTP error: ${response.status}`);
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
- throw mapToCustomError(err);
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
- await handleError(err);
331
- throw new Error("Unreachable: handleError should always throw");
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 $=i=>{let e=i.response?.headers.get("Retry-After");if(e){let o=parseInt(e,10);if(!isNaN(o))return o*1e3;let a=Date.parse(e);if(!isNaN(a))return Math.max(0,a-Date.now())}return 2**i.attempt*200+Math.random()*100};async function z(i,e,o,a=()=>!0,F){let f,d;for(let y=0;y<=e;y++){let s={attempt:y+1,request:F,response:d,error:f};try{if(d=await i(),s.response=d,s.error=void 0,y<e&&a(s)){let c=typeof o=="function"?o(s):o;await new Promise(O=>setTimeout(O,c));continue}return d}catch(c){if(f=c,s.error=c,y===e||!a(s))throw c;let O=typeof o=="function"?o(s):o;await new Promise(g=>setTimeout(g,O))}}throw f}var b=class extends Error{constructor(e,o,a){super(o),this.name=e,a!==void 0&&(this.cause=a)}},p=class extends b{constructor(e="Request timed out",o){super("TimeoutError",e,o)}},h=class extends b{constructor(e="Circuit is open",o){super("CircuitOpenError",e,o)}},l=class extends b{constructor(e="Request was aborted",o){super("AbortError",e,o)}},C=class extends b{constructor(e="Retry limit reached",o){super("RetryLimitError",e,o)}},E=class extends b{constructor(e="Network error occurred",o){super("NetworkError",e,o)}};function G(i){let{error:e,response:o}=i;return e instanceof l||e instanceof h||e instanceof p?!1:o?o.status>=500||o.status===429:!0}var S=class{constructor(e,o){this.threshold=e;this.resetTimeout=o;this.failures=0;this.nextAttempt=0;this.isOpen=!1}get open(){return this.isOpen}recordResult(e,o,a){return o&&!(o instanceof C)?(this.setLastOpenRequest(a),this.onFailure(),!0):e&&(e.status>=500||e.status===429)?(this.setLastOpenRequest(a),this.onFailure(),!0):(a&&this.setLastSuccessRequest(a),this.onSuccess(),!1)}setHooks(e){this.hooks=e}setLastOpenRequest(e){this.lastOpenRequest=e}setLastSuccessRequest(e){this.lastSuccessRequest=e}async invoke(e){if(Date.now()<this.nextAttempt)throw new h("Circuit is open");try{let o=await e();return this.onSuccess(),o}catch(o){throw this.onFailure(),o}}onSuccess(){let e=this.isOpen;this.failures=0,e&&(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(i={}){let{timeout:e=5e3,retries:o=0,retryDelay:a=$,shouldRetry:F=G,hooks:f={},circuit:d,fetchHandler:y}=i,s=d?new S(d.threshold,d.reset):null;s&&(f.onCircuitClose||f.onCircuitOpen)&&s.setHooks({onCircuitClose:f.onCircuitClose,onCircuitOpen:f.onCircuitOpen});let c=[];function O(){for(let D of c)D.controller?.abort()}let g=async(D,m={})=>{let r=new Request(D,m),n={...f,...m.hooks||{}};n.transformRequest&&(r=await n.transformRequest(r)),await n.before?.(r);function J(u){if(typeof AbortSignal?.timeout=="function")return AbortSignal.timeout(u);let v=new AbortController,L=setTimeout(()=>v.abort(),u);return v.signal.addEventListener("abort",()=>clearTimeout(L),{once:!0}),v.signal}let I=m.timeout??e,q=m.signal,T=r.signal,A,k,P;I>0&&(A=J(I));let x=[];if(q&&x.push(q),T&&T!==q&&x.push(T),A&&x.push(A),x.length===1)k=x[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.");k=AbortSignal.any(x),P=new AbortController}let M=async()=>{let u=m.retries??o,v=typeof m.retryDelay<"u"?m.retryDelay:a,L=m.shouldRetry??F,H=0,K=t=>{H=t.attempt;let w=L(t);return w&&H<=u&&n.onRetry?.(r,H-1,t.error,t.response),w};function _(t){return t instanceof DOMException&&t.name==="AbortError"?A?.aborted&&(!q||!q.aborted)?new p("signal timed out",t):new l("Request was aborted",t):t instanceof TypeError&&/NetworkError|network error|failed to fetch|lost connection|NetworkError when attempting to fetch resource/i.test(t.message)?new E(t.message,t):t}async function Q(t){if(t=_(t),q?.aborted){let R=new l("Request was aborted by user");throw await n.onAbort?.(r),await n.onError?.(r,R),await n.onComplete?.(r,void 0,R),R}if(t instanceof p||t instanceof E||t instanceof l)throw t instanceof p&&await n.onTimeout?.(r),t instanceof l&&await n.onAbort?.(r),await n.onError?.(r,t),await n.onComplete?.(r,void 0,t),t;let w=new C(typeof t=="object"&&t&&"message"in t&&typeof t.message=="string"?t.message:"Retry limit reached");throw await n.onError?.(r,w),await n.onComplete?.(r,void 0,w),w}try{let t=await z(async()=>{if(typeof k?.throwIfAborted=="function")k.throwIfAborted();else if(k?.aborted)throw new l("Request was aborted");let w=new Request(r,{signal:k});try{let N=await(y??fetch)(w);if(s&&s.recordResult(N,void 0,r))throw new Error(`HTTP error: ${N.status}`);return N}catch(R){throw s&&s.recordResult(void 0,R,r),_(R)}},u,v,K,r);return n.transformResponse&&(t=await n.transformResponse(t,r)),await n.after?.(r,t),await n.onComplete?.(r,t,void 0),t}catch(t){throw await Q(t),new Error("Unreachable: handleError should always throw")}},W=s?s.invoke(M).catch(async u=>{throw u instanceof h?(await n.onCircuitOpen?.(r),await n.onError?.(r,u),await n.onComplete?.(r,void 0,u)):(await n.onError?.(r,u),await n.onComplete?.(r,void 0,u)),u}):M(),U={promise:W,request:r,controller:P};return c.push(U),W.finally(()=>{let u=c.indexOf(U);u>-1&&c.splice(u,1)})};return Object.defineProperty(g,"pendingRequests",{get(){return c},enumerable:!1,configurable:!1}),Object.defineProperty(g,"abortAll",{value:O,writable:!1,enumerable:!1,configurable:!1}),Object.defineProperty(g,"circuitOpen",{get(){return s?s.open:!1},enumerable:!0}),g}var ae=j;export{l as AbortError,h as CircuitOpenError,E as NetworkError,C as RetryLimitError,p as TimeoutError,j as createClient,ae as default};
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
@@ -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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fetchkit/ffetch",
3
- "version": "3.4.1",
3
+ "version": "4.0.0",
4
4
  "description": "Fetch wrapper with configurable timeouts, retries, and TypeScript-first DX",
5
5
  "keywords": [
6
6
  "fetch",