@fetchkit/ffetch 3.4.1
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/LICENSE +21 -0
- package/README.md +176 -0
- package/dist/index.cjs +423 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +72 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.js +391 -0
- package/dist/index.js.map +1 -0
- package/dist/index.min.js +2 -0
- package/dist/index.min.js.map +1 -0
- package/package.json +65 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
type Hooks = {
|
|
2
|
+
before?: (req: Request) => void | Promise<void>;
|
|
3
|
+
after?: (req: Request, res: Response) => void | Promise<void>;
|
|
4
|
+
onError?: (req: Request, err: unknown) => void | Promise<void>;
|
|
5
|
+
onRetry?: (req: Request, attempt: number, err?: unknown, res?: Response) => void | Promise<void>;
|
|
6
|
+
onTimeout?: (req: Request) => void | Promise<void>;
|
|
7
|
+
onAbort?: (req: Request) => void | Promise<void>;
|
|
8
|
+
onCircuitOpen?: (req: Request) => void | Promise<void>;
|
|
9
|
+
onCircuitClose?: (req: Request) => void | Promise<void>;
|
|
10
|
+
onComplete?: (req: Request, res?: Response, err?: unknown) => void | Promise<void>;
|
|
11
|
+
transformRequest?: (req: Request) => Request | Promise<Request>;
|
|
12
|
+
transformResponse?: (res: Response, req: Request) => Response | Promise<Response>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
interface RetryContext {
|
|
16
|
+
attempt: number;
|
|
17
|
+
request: Request;
|
|
18
|
+
response?: Response;
|
|
19
|
+
error?: unknown;
|
|
20
|
+
}
|
|
21
|
+
interface FFetchOptions {
|
|
22
|
+
timeout?: number;
|
|
23
|
+
retries?: number;
|
|
24
|
+
retryDelay?: number | ((ctx: RetryContext) => number);
|
|
25
|
+
shouldRetry?: (ctx: RetryContext) => boolean;
|
|
26
|
+
circuit?: {
|
|
27
|
+
threshold: number;
|
|
28
|
+
reset: number;
|
|
29
|
+
};
|
|
30
|
+
hooks?: Hooks;
|
|
31
|
+
fetchHandler?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
32
|
+
}
|
|
33
|
+
type FFetch = {
|
|
34
|
+
(input: RequestInfo | URL, init?: FFetchRequestInit): Promise<Response>;
|
|
35
|
+
pendingRequests: PendingRequest[];
|
|
36
|
+
abortAll: () => void;
|
|
37
|
+
/**
|
|
38
|
+
* True if the circuit breaker is open (blocking requests), false otherwise.
|
|
39
|
+
*/
|
|
40
|
+
circuitOpen: boolean;
|
|
41
|
+
};
|
|
42
|
+
interface FFetchRequestInit extends RequestInit, FFetchOptions {
|
|
43
|
+
}
|
|
44
|
+
type PendingRequest = {
|
|
45
|
+
promise: Promise<Response>;
|
|
46
|
+
request: Request;
|
|
47
|
+
controller?: AbortController;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
declare function createClient(opts?: FFetchOptions): FFetch;
|
|
51
|
+
|
|
52
|
+
declare class BaseError extends Error {
|
|
53
|
+
cause?: unknown;
|
|
54
|
+
constructor(name: string, message: string, cause?: unknown);
|
|
55
|
+
}
|
|
56
|
+
declare class TimeoutError extends BaseError {
|
|
57
|
+
constructor(message?: string, cause?: unknown);
|
|
58
|
+
}
|
|
59
|
+
declare class CircuitOpenError extends BaseError {
|
|
60
|
+
constructor(message?: string, cause?: unknown);
|
|
61
|
+
}
|
|
62
|
+
declare class AbortError extends BaseError {
|
|
63
|
+
constructor(message?: string, cause?: unknown);
|
|
64
|
+
}
|
|
65
|
+
declare class RetryLimitError extends BaseError {
|
|
66
|
+
constructor(message?: string, cause?: unknown);
|
|
67
|
+
}
|
|
68
|
+
declare class NetworkError extends BaseError {
|
|
69
|
+
constructor(message?: string, cause?: unknown);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export { AbortError, CircuitOpenError, type FFetch, type FFetchOptions, type Hooks, NetworkError, RetryLimitError, TimeoutError, createClient, createClient as default };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
type Hooks = {
|
|
2
|
+
before?: (req: Request) => void | Promise<void>;
|
|
3
|
+
after?: (req: Request, res: Response) => void | Promise<void>;
|
|
4
|
+
onError?: (req: Request, err: unknown) => void | Promise<void>;
|
|
5
|
+
onRetry?: (req: Request, attempt: number, err?: unknown, res?: Response) => void | Promise<void>;
|
|
6
|
+
onTimeout?: (req: Request) => void | Promise<void>;
|
|
7
|
+
onAbort?: (req: Request) => void | Promise<void>;
|
|
8
|
+
onCircuitOpen?: (req: Request) => void | Promise<void>;
|
|
9
|
+
onCircuitClose?: (req: Request) => void | Promise<void>;
|
|
10
|
+
onComplete?: (req: Request, res?: Response, err?: unknown) => void | Promise<void>;
|
|
11
|
+
transformRequest?: (req: Request) => Request | Promise<Request>;
|
|
12
|
+
transformResponse?: (res: Response, req: Request) => Response | Promise<Response>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
interface RetryContext {
|
|
16
|
+
attempt: number;
|
|
17
|
+
request: Request;
|
|
18
|
+
response?: Response;
|
|
19
|
+
error?: unknown;
|
|
20
|
+
}
|
|
21
|
+
interface FFetchOptions {
|
|
22
|
+
timeout?: number;
|
|
23
|
+
retries?: number;
|
|
24
|
+
retryDelay?: number | ((ctx: RetryContext) => number);
|
|
25
|
+
shouldRetry?: (ctx: RetryContext) => boolean;
|
|
26
|
+
circuit?: {
|
|
27
|
+
threshold: number;
|
|
28
|
+
reset: number;
|
|
29
|
+
};
|
|
30
|
+
hooks?: Hooks;
|
|
31
|
+
fetchHandler?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
32
|
+
}
|
|
33
|
+
type FFetch = {
|
|
34
|
+
(input: RequestInfo | URL, init?: FFetchRequestInit): Promise<Response>;
|
|
35
|
+
pendingRequests: PendingRequest[];
|
|
36
|
+
abortAll: () => void;
|
|
37
|
+
/**
|
|
38
|
+
* True if the circuit breaker is open (blocking requests), false otherwise.
|
|
39
|
+
*/
|
|
40
|
+
circuitOpen: boolean;
|
|
41
|
+
};
|
|
42
|
+
interface FFetchRequestInit extends RequestInit, FFetchOptions {
|
|
43
|
+
}
|
|
44
|
+
type PendingRequest = {
|
|
45
|
+
promise: Promise<Response>;
|
|
46
|
+
request: Request;
|
|
47
|
+
controller?: AbortController;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
declare function createClient(opts?: FFetchOptions): FFetch;
|
|
51
|
+
|
|
52
|
+
declare class BaseError extends Error {
|
|
53
|
+
cause?: unknown;
|
|
54
|
+
constructor(name: string, message: string, cause?: unknown);
|
|
55
|
+
}
|
|
56
|
+
declare class TimeoutError extends BaseError {
|
|
57
|
+
constructor(message?: string, cause?: unknown);
|
|
58
|
+
}
|
|
59
|
+
declare class CircuitOpenError extends BaseError {
|
|
60
|
+
constructor(message?: string, cause?: unknown);
|
|
61
|
+
}
|
|
62
|
+
declare class AbortError extends BaseError {
|
|
63
|
+
constructor(message?: string, cause?: unknown);
|
|
64
|
+
}
|
|
65
|
+
declare class RetryLimitError extends BaseError {
|
|
66
|
+
constructor(message?: string, cause?: unknown);
|
|
67
|
+
}
|
|
68
|
+
declare class NetworkError extends BaseError {
|
|
69
|
+
constructor(message?: string, cause?: unknown);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export { AbortError, CircuitOpenError, type FFetch, type FFetchOptions, type Hooks, NetworkError, RetryLimitError, TimeoutError, createClient, createClient as default };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
// src/retry.ts
|
|
2
|
+
var defaultDelay = (ctx) => {
|
|
3
|
+
const retryAfter = ctx.response?.headers.get("Retry-After");
|
|
4
|
+
if (retryAfter) {
|
|
5
|
+
const seconds = parseInt(retryAfter, 10);
|
|
6
|
+
if (!isNaN(seconds)) return seconds * 1e3;
|
|
7
|
+
const date = Date.parse(retryAfter);
|
|
8
|
+
if (!isNaN(date)) return Math.max(0, date - Date.now());
|
|
9
|
+
}
|
|
10
|
+
return 2 ** ctx.attempt * 200 + Math.random() * 100;
|
|
11
|
+
};
|
|
12
|
+
async function retry(fn, retries, delay, shouldRetry2 = () => true, request) {
|
|
13
|
+
let lastErr;
|
|
14
|
+
let lastRes;
|
|
15
|
+
for (let i = 0; i <= retries; i++) {
|
|
16
|
+
const ctx = {
|
|
17
|
+
attempt: i + 1,
|
|
18
|
+
request,
|
|
19
|
+
response: lastRes,
|
|
20
|
+
error: lastErr
|
|
21
|
+
};
|
|
22
|
+
try {
|
|
23
|
+
lastRes = await fn();
|
|
24
|
+
ctx.response = lastRes;
|
|
25
|
+
ctx.error = void 0;
|
|
26
|
+
if (i < retries && shouldRetry2(ctx)) {
|
|
27
|
+
const wait = typeof delay === "function" ? delay(ctx) : delay;
|
|
28
|
+
await new Promise((r) => setTimeout(r, wait));
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
return lastRes;
|
|
32
|
+
} catch (err) {
|
|
33
|
+
lastErr = err;
|
|
34
|
+
ctx.error = err;
|
|
35
|
+
if (i === retries || !shouldRetry2(ctx)) throw err;
|
|
36
|
+
const wait = typeof delay === "function" ? delay(ctx) : delay;
|
|
37
|
+
await new Promise((r) => setTimeout(r, wait));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
throw lastErr;
|
|
41
|
+
}
|
|
42
|
+
|
|
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
|
+
// src/should-retry.ts
|
|
78
|
+
function shouldRetry(ctx) {
|
|
79
|
+
const { error, response } = ctx;
|
|
80
|
+
if (error instanceof AbortError || error instanceof CircuitOpenError || error instanceof TimeoutError)
|
|
81
|
+
return false;
|
|
82
|
+
if (!response) return true;
|
|
83
|
+
return response.status >= 500 || response.status === 429;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/circuit.ts
|
|
87
|
+
var CircuitBreaker = class {
|
|
88
|
+
constructor(threshold, resetTimeout) {
|
|
89
|
+
this.threshold = threshold;
|
|
90
|
+
this.resetTimeout = resetTimeout;
|
|
91
|
+
this.failures = 0;
|
|
92
|
+
this.nextAttempt = 0;
|
|
93
|
+
this.isOpen = false;
|
|
94
|
+
}
|
|
95
|
+
// Returns true if the circuit breaker is currently open (blocking requests).
|
|
96
|
+
get open() {
|
|
97
|
+
return this.isOpen;
|
|
98
|
+
}
|
|
99
|
+
// Call this after each request to record the result and update circuit state.
|
|
100
|
+
// Returns true if the result was counted as a failure.
|
|
101
|
+
recordResult(response, error, req) {
|
|
102
|
+
if (error && !(error instanceof RetryLimitError)) {
|
|
103
|
+
this.setLastOpenRequest(req);
|
|
104
|
+
this.onFailure();
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
if (response && (response.status >= 500 || response.status === 429)) {
|
|
108
|
+
this.setLastOpenRequest(req);
|
|
109
|
+
this.onFailure();
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
if (req) this.setLastSuccessRequest(req);
|
|
113
|
+
this.onSuccess();
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
setHooks(hooks) {
|
|
117
|
+
this.hooks = hooks;
|
|
118
|
+
}
|
|
119
|
+
setLastOpenRequest(req) {
|
|
120
|
+
this.lastOpenRequest = req;
|
|
121
|
+
}
|
|
122
|
+
setLastSuccessRequest(req) {
|
|
123
|
+
this.lastSuccessRequest = req;
|
|
124
|
+
}
|
|
125
|
+
async invoke(fn) {
|
|
126
|
+
if (Date.now() < this.nextAttempt)
|
|
127
|
+
throw new CircuitOpenError("Circuit is open");
|
|
128
|
+
try {
|
|
129
|
+
const res = await fn();
|
|
130
|
+
this.onSuccess();
|
|
131
|
+
return res;
|
|
132
|
+
} catch (err) {
|
|
133
|
+
this.onFailure();
|
|
134
|
+
throw err;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
onSuccess() {
|
|
138
|
+
const wasOpen = this.isOpen;
|
|
139
|
+
this.failures = 0;
|
|
140
|
+
if (wasOpen) {
|
|
141
|
+
this.isOpen = false;
|
|
142
|
+
if (this.hooks?.onCircuitClose && this.lastSuccessRequest) {
|
|
143
|
+
this.hooks.onCircuitClose(this.lastSuccessRequest);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
this.lastSuccessRequest = void 0;
|
|
147
|
+
}
|
|
148
|
+
onFailure() {
|
|
149
|
+
this.failures++;
|
|
150
|
+
if (this.failures >= this.threshold) {
|
|
151
|
+
this.nextAttempt = Date.now() + this.resetTimeout;
|
|
152
|
+
this.isOpen = true;
|
|
153
|
+
if (this.hooks?.onCircuitOpen && this.lastOpenRequest) {
|
|
154
|
+
this.hooks.onCircuitOpen(this.lastOpenRequest);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// src/client.ts
|
|
161
|
+
function createClient(opts = {}) {
|
|
162
|
+
const {
|
|
163
|
+
timeout: clientDefaultTimeout = 5e3,
|
|
164
|
+
retries: clientDefaultRetries = 0,
|
|
165
|
+
retryDelay: clientDefaultRetryDelay = defaultDelay,
|
|
166
|
+
shouldRetry: clientDefaultShouldRetry = shouldRetry,
|
|
167
|
+
hooks: clientDefaultHooks = {},
|
|
168
|
+
circuit: clientDefaultCircuit,
|
|
169
|
+
fetchHandler
|
|
170
|
+
} = opts;
|
|
171
|
+
const breaker = clientDefaultCircuit ? new CircuitBreaker(
|
|
172
|
+
clientDefaultCircuit.threshold,
|
|
173
|
+
clientDefaultCircuit.reset
|
|
174
|
+
) : null;
|
|
175
|
+
if (breaker && (clientDefaultHooks.onCircuitClose || clientDefaultHooks.onCircuitOpen)) {
|
|
176
|
+
breaker.setHooks({
|
|
177
|
+
onCircuitClose: clientDefaultHooks.onCircuitClose,
|
|
178
|
+
onCircuitOpen: clientDefaultHooks.onCircuitOpen
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
const pendingRequests = [];
|
|
182
|
+
function abortAll() {
|
|
183
|
+
for (const entry of pendingRequests) {
|
|
184
|
+
entry.controller?.abort();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const client = async (input, init = {}) => {
|
|
188
|
+
let request = new Request(input, init);
|
|
189
|
+
const effectiveHooks = { ...clientDefaultHooks, ...init.hooks || {} };
|
|
190
|
+
if (effectiveHooks.transformRequest) {
|
|
191
|
+
request = await effectiveHooks.transformRequest(request);
|
|
192
|
+
}
|
|
193
|
+
await effectiveHooks.before?.(request);
|
|
194
|
+
function createTimeoutSignal(timeout) {
|
|
195
|
+
if (typeof AbortSignal?.timeout === "function") {
|
|
196
|
+
return AbortSignal.timeout(timeout);
|
|
197
|
+
}
|
|
198
|
+
const controller2 = new AbortController();
|
|
199
|
+
const timeoutId = setTimeout(() => controller2.abort(), timeout);
|
|
200
|
+
controller2.signal.addEventListener(
|
|
201
|
+
"abort",
|
|
202
|
+
() => clearTimeout(timeoutId),
|
|
203
|
+
{ once: true }
|
|
204
|
+
);
|
|
205
|
+
return controller2.signal;
|
|
206
|
+
}
|
|
207
|
+
const effectiveTimeout = init.timeout ?? clientDefaultTimeout;
|
|
208
|
+
const userSignal = init.signal;
|
|
209
|
+
const transformedSignal = request.signal;
|
|
210
|
+
let timeoutSignal = void 0;
|
|
211
|
+
let combinedSignal = void 0;
|
|
212
|
+
let controller = void 0;
|
|
213
|
+
if (effectiveTimeout > 0) {
|
|
214
|
+
timeoutSignal = createTimeoutSignal(effectiveTimeout);
|
|
215
|
+
}
|
|
216
|
+
const signals = [];
|
|
217
|
+
if (userSignal) signals.push(userSignal);
|
|
218
|
+
if (transformedSignal && transformedSignal !== userSignal) {
|
|
219
|
+
signals.push(transformedSignal);
|
|
220
|
+
}
|
|
221
|
+
if (timeoutSignal) signals.push(timeoutSignal);
|
|
222
|
+
if (signals.length === 1) {
|
|
223
|
+
combinedSignal = signals[0];
|
|
224
|
+
controller = new AbortController();
|
|
225
|
+
} else {
|
|
226
|
+
if (typeof AbortSignal.any !== "function") {
|
|
227
|
+
throw new Error(
|
|
228
|
+
"AbortSignal.any is required for combining multiple signals. Please install a polyfill for environments that do not support it."
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
combinedSignal = AbortSignal.any(signals);
|
|
232
|
+
controller = new AbortController();
|
|
233
|
+
}
|
|
234
|
+
const retryWithHooks = async () => {
|
|
235
|
+
const effectiveRetries = init.retries ?? clientDefaultRetries;
|
|
236
|
+
const effectiveRetryDelay = typeof init.retryDelay !== "undefined" ? init.retryDelay : clientDefaultRetryDelay;
|
|
237
|
+
const effectiveShouldRetry = init.shouldRetry ?? clientDefaultShouldRetry;
|
|
238
|
+
let attempt = 0;
|
|
239
|
+
const shouldRetryWithHook = (ctx) => {
|
|
240
|
+
attempt = ctx.attempt;
|
|
241
|
+
const retrying = effectiveShouldRetry(ctx);
|
|
242
|
+
if (retrying && attempt <= effectiveRetries) {
|
|
243
|
+
effectiveHooks.onRetry?.(
|
|
244
|
+
request,
|
|
245
|
+
attempt - 1,
|
|
246
|
+
ctx.error,
|
|
247
|
+
ctx.response
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
return retrying;
|
|
251
|
+
};
|
|
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
|
+
}
|
|
293
|
+
try {
|
|
294
|
+
let res = await retry(
|
|
295
|
+
async () => {
|
|
296
|
+
if (typeof combinedSignal?.throwIfAborted === "function") {
|
|
297
|
+
combinedSignal.throwIfAborted();
|
|
298
|
+
} else if (combinedSignal?.aborted) {
|
|
299
|
+
throw new AbortError("Request was aborted");
|
|
300
|
+
}
|
|
301
|
+
const reqWithSignal = new Request(request, {
|
|
302
|
+
signal: combinedSignal
|
|
303
|
+
});
|
|
304
|
+
try {
|
|
305
|
+
const handler = fetchHandler ?? fetch;
|
|
306
|
+
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
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return response;
|
|
313
|
+
} catch (err) {
|
|
314
|
+
if (breaker) breaker.recordResult(void 0, err, request);
|
|
315
|
+
throw mapToCustomError(err);
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
effectiveRetries,
|
|
319
|
+
effectiveRetryDelay,
|
|
320
|
+
shouldRetryWithHook,
|
|
321
|
+
request
|
|
322
|
+
);
|
|
323
|
+
if (effectiveHooks.transformResponse) {
|
|
324
|
+
res = await effectiveHooks.transformResponse(res, request);
|
|
325
|
+
}
|
|
326
|
+
await effectiveHooks.after?.(request, res);
|
|
327
|
+
await effectiveHooks.onComplete?.(request, res, void 0);
|
|
328
|
+
return res;
|
|
329
|
+
} catch (err) {
|
|
330
|
+
await handleError(err);
|
|
331
|
+
throw new Error("Unreachable: handleError should always throw");
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
const promise = breaker ? breaker.invoke(retryWithHooks).catch(async (err) => {
|
|
335
|
+
if (err instanceof CircuitOpenError) {
|
|
336
|
+
await effectiveHooks.onCircuitOpen?.(request);
|
|
337
|
+
await effectiveHooks.onError?.(request, err);
|
|
338
|
+
await effectiveHooks.onComplete?.(request, void 0, err);
|
|
339
|
+
} else {
|
|
340
|
+
await effectiveHooks.onError?.(request, err);
|
|
341
|
+
await effectiveHooks.onComplete?.(request, void 0, err);
|
|
342
|
+
}
|
|
343
|
+
throw err;
|
|
344
|
+
}) : retryWithHooks();
|
|
345
|
+
const entry = {
|
|
346
|
+
promise,
|
|
347
|
+
request,
|
|
348
|
+
controller
|
|
349
|
+
};
|
|
350
|
+
pendingRequests.push(entry);
|
|
351
|
+
return promise.finally(() => {
|
|
352
|
+
const index = pendingRequests.indexOf(entry);
|
|
353
|
+
if (index > -1) {
|
|
354
|
+
pendingRequests.splice(index, 1);
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
};
|
|
358
|
+
Object.defineProperty(client, "pendingRequests", {
|
|
359
|
+
get() {
|
|
360
|
+
return pendingRequests;
|
|
361
|
+
},
|
|
362
|
+
enumerable: false,
|
|
363
|
+
configurable: false
|
|
364
|
+
});
|
|
365
|
+
Object.defineProperty(client, "abortAll", {
|
|
366
|
+
value: abortAll,
|
|
367
|
+
writable: false,
|
|
368
|
+
enumerable: false,
|
|
369
|
+
configurable: false
|
|
370
|
+
});
|
|
371
|
+
Object.defineProperty(client, "circuitOpen", {
|
|
372
|
+
get() {
|
|
373
|
+
return breaker ? breaker.open : false;
|
|
374
|
+
},
|
|
375
|
+
enumerable: true
|
|
376
|
+
});
|
|
377
|
+
return client;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// src/index.ts
|
|
381
|
+
var index_default = createClient;
|
|
382
|
+
export {
|
|
383
|
+
AbortError,
|
|
384
|
+
CircuitOpenError,
|
|
385
|
+
NetworkError,
|
|
386
|
+
RetryLimitError,
|
|
387
|
+
TimeoutError,
|
|
388
|
+
createClient,
|
|
389
|
+
index_default as default
|
|
390
|
+
};
|
|
391
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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"]}
|
|
@@ -0,0 +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};
|
|
2
|
+
//# sourceMappingURL=index.min.js.map
|
|
@@ -0,0 +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"]}
|