vigor-fetch 1.0.2 → 1.0.3
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 +107 -38
- package/dist/index.d.mts +75 -0
- package/dist/index.d.ts +58 -25
- package/dist/index.js +276 -152
- package/dist/index.mjs +261 -0
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -8,57 +8,126 @@
|
|
|
8
8
|
- 📈 **Exponential Backoff & Jitter:** Prevents server hammering by increasing wait times with random variation.
|
|
9
9
|
- ⚡ **Zero Dependencies:** Built using native Fetch API and AbortController.
|
|
10
10
|
- 🎯 **Fully Type-Safe:** Written in TypeScript for excellent developer experience and auto-completion.
|
|
11
|
+
- 🚦 **Concurrency Control**: Execute bulk requests with a global limit and inter-request jitter using the VigorAll engine.
|
|
12
|
+
- 🎯 **Immutable Chaining**: Every configuration method returns a new instance, making it perfect for base templates and functional patterns.
|
|
11
13
|
|
|
12
14
|
## 📦 Installation
|
|
13
15
|
|
|
14
16
|
npm install vigor-fetch
|
|
15
17
|
|
|
18
|
+
## 🛠️ API References
|
|
19
|
+
|
|
20
|
+
1. Vigor().fetch(origin, config)
|
|
21
|
+
|
|
22
|
+
| Method | Type | Default | Description
|
|
23
|
+
| :--- | :--- | :--- | :--- |
|
|
24
|
+
| .path(arg) | string | "" | Sets the endpoint path to be appended to the origin. |
|
|
25
|
+
| .query(arg) | Record<string, any> | {} | Appends key-value pairs as query parameters to the URL. |
|
|
26
|
+
| .headers(arg) | Record<string, string> | {} | Sets the HTTP request headers. |
|
|
27
|
+
| .body(arg) | any | null | Sets the request body |
|
|
28
|
+
| .offset(arg) | RequestInit | {} | Provides raw fetch options to be merged into the request. |
|
|
29
|
+
| .count(arg) | number | 5 | Specifies the maximum number of retry attempts for the request. |
|
|
30
|
+
| .max(arg) | number | 5000 | Sets the timeout (in ms) for each individual request attempt. |
|
|
31
|
+
| .wait(arg) | number | 10000 | Sets the maximum wait time (in ms) between retry attempts. |
|
|
32
|
+
| .backoff(arg) | number | 1.3 | The multiplier used for exponential backoff between retries. |
|
|
33
|
+
| .jitter(arg) | number | 500 | Adds a random delay (up to this value in ms) to prevent thundering herd issues. |
|
|
34
|
+
| .unretry(arg[]) | number[] | [400, 401, 403, 404, 405, 413, 422] | List of HTTP status codes that should NOT trigger a retry. |
|
|
35
|
+
| .retryHeader(...arg) | string[] | ['retry-after', ...] | Custom headers to check for server-defined retry delays |
|
|
36
|
+
| .original(bool) | boolean | false | If true, returns the raw Response object instead of parsed data. |
|
|
37
|
+
| .parse(key) | keyof Response | null | Forces the use of a specific Response method (e.g., 'json', 'blob') for parsing. |
|
|
38
|
+
| .beforeRequest(...hooks) | Function[] | [] | Hooks executed to modify request options before the fetch occurs. |
|
|
39
|
+
| .afterRequest(...hooks) | Function[] | [] | Hooks executed immediately after the fetch, before the status check. |
|
|
40
|
+
| .beforeResponse(...hooks) | Function[] | [] | Hooks executed on the Response object before parsing the body. |
|
|
41
|
+
| .afterResponse(...hooks) | Function[] | [] | Hooks executed on the parsed data before returning the final result. |
|
|
42
|
+
| .onError(...hooks) | Function[] | [] | Hooks to handle errors; can recover from error by returning a value. |
|
|
43
|
+
| .request() | Promise<T> | - | Executes the request logic including retries and hooks. |
|
|
44
|
+
|
|
45
|
+
2. Vigor().all(config)
|
|
46
|
+
| Method | Type | Default | Description
|
|
47
|
+
| :--- | :--- | :--- | :--- |
|
|
48
|
+
| .limit(arg) | number | 10 | Maximum number of concurrent promises running at once. |
|
|
49
|
+
| .jitter(arg) | number | 1000 | Random delay (ms) applied before each task starts. |
|
|
50
|
+
| .promises(...args) | Function[] | [] | Adds functions that return promises to the execution queue. |
|
|
51
|
+
| .request() | Promise<any[]> | - | Executes all tasks with concurrency control and returns results. |
|
|
52
|
+
|
|
16
53
|
## 🚀 Quick Start
|
|
17
54
|
|
|
18
|
-
```
|
|
55
|
+
```javascript
|
|
19
56
|
|
|
20
57
|
import Vigor from 'vigor-fetch';
|
|
21
58
|
|
|
22
|
-
const
|
|
23
|
-
|
|
59
|
+
const vigor = new Vigor();
|
|
60
|
+
|
|
61
|
+
const data = await vigor.fetch("https://api.example.com")
|
|
62
|
+
.path("/v1/users")
|
|
24
63
|
.method("POST")
|
|
25
|
-
.headers({ "
|
|
26
|
-
.query({ limit: 10, page: 1 })
|
|
64
|
+
.headers({ "Authorization": "Bearer TOKEN" })
|
|
27
65
|
.body({ name: "Uav1010" })
|
|
28
|
-
.count(5)
|
|
29
|
-
.
|
|
30
|
-
.
|
|
31
|
-
.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
66
|
+
.count(5) // Retry up to 5 times
|
|
67
|
+
.backoff(1.5) // Multiply wait time by 1.5x each failure
|
|
68
|
+
.parse("json") // Auto-parse response as JSON
|
|
69
|
+
.request();
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 🛠️ Advanced Patterns
|
|
74
|
+
|
|
75
|
+
1. Request Templates
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
|
|
79
|
+
import Vigor from 'vigor-fetch';
|
|
80
|
+
|
|
81
|
+
const vigor = new Vigor();
|
|
82
|
+
|
|
83
|
+
const apiClient = vigor.fetch("https://api.myapp.com")
|
|
84
|
+
.headers({ "Content-Type": "application/json" })
|
|
85
|
+
.unretry([401, 403, 404]) // Don't retry on these statuses
|
|
86
|
+
.max(3000); // 3s timeout per attempt
|
|
87
|
+
|
|
88
|
+
const user = await apiClient.path("/me").request();
|
|
89
|
+
const settings = await apiClient.path("/settings").request();
|
|
41
90
|
|
|
42
|
-
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
2. Batch Processing with Concurrency Limit
|
|
94
|
+
|
|
95
|
+
```javascript
|
|
96
|
+
|
|
97
|
+
import Vigor from 'vigor-fetch';
|
|
98
|
+
|
|
99
|
+
const vigor = new Vigor();
|
|
100
|
+
|
|
101
|
+
const tasks = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(id => () =>
|
|
102
|
+
vigor.fetch("https://api.com").path(`/data/${id}`).request()
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const results = await vigor.all()
|
|
106
|
+
.limit(3) // Max 3 concurrent requests
|
|
107
|
+
.jitter(500) // Add up to 500ms random delay between starts
|
|
108
|
+
.promises(...tasks)
|
|
109
|
+
.request();
|
|
43
110
|
|
|
44
111
|
```
|
|
45
112
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
113
|
+
3. Middleware & Hooks
|
|
114
|
+
|
|
115
|
+
```javascript
|
|
116
|
+
|
|
117
|
+
import Vigor from 'vigor-fetch';
|
|
118
|
+
|
|
119
|
+
const vigor = new Vigor();
|
|
120
|
+
|
|
121
|
+
const api = vigor.fetch("https://api.com")
|
|
122
|
+
.beforeRequest((opt) => {
|
|
123
|
+
opt.headers = { ...opt.headers, "X-Timestamp": Date.now().toString() };
|
|
124
|
+
})
|
|
125
|
+
.afterResponse((data) => {
|
|
126
|
+
return { ...data, receivedAt: new Date() }; // Transform final result
|
|
127
|
+
})
|
|
128
|
+
.onError((err) => {
|
|
129
|
+
if (err.status === 404) return { error: "Not Found", fallback: true };
|
|
130
|
+
throw err; // Continue throwing if not handled
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
```
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
interface VigorErrorOptions {
|
|
2
|
+
url?: string | null;
|
|
3
|
+
status?: number;
|
|
4
|
+
message?: string;
|
|
5
|
+
data?: any;
|
|
6
|
+
}
|
|
7
|
+
interface VigorFetchConfig {
|
|
8
|
+
path: string;
|
|
9
|
+
method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD" | null;
|
|
10
|
+
offset: RequestInit;
|
|
11
|
+
headers: Record<string, string>;
|
|
12
|
+
body: any;
|
|
13
|
+
count: number;
|
|
14
|
+
max: number;
|
|
15
|
+
wait: number;
|
|
16
|
+
backoff: number;
|
|
17
|
+
unretry: Set<number>;
|
|
18
|
+
retryHeader: string[];
|
|
19
|
+
original: boolean;
|
|
20
|
+
parse: keyof Response | null;
|
|
21
|
+
beforeRequest: Array<(opt: RequestInit) => Promise<Partial<RequestInit> | void> | Partial<RequestInit> | void>;
|
|
22
|
+
afterRequest: Array<(res: Response) => Promise<Response | void> | Response | void>;
|
|
23
|
+
beforeResponse: Array<(res: Response) => Promise<Response> | Response>;
|
|
24
|
+
afterResponse: Array<(data: any) => Promise<any> | any>;
|
|
25
|
+
onError: Array<(err: any) => Promise<any> | any>;
|
|
26
|
+
query: Record<string, any>;
|
|
27
|
+
jitter: number;
|
|
28
|
+
}
|
|
29
|
+
declare class VigorFetch<T = any> {
|
|
30
|
+
private _origin;
|
|
31
|
+
private _config;
|
|
32
|
+
constructor(origin: string, config?: VigorFetchConfig);
|
|
33
|
+
private _next;
|
|
34
|
+
path(arg: string): VigorFetch<T>;
|
|
35
|
+
method(arg: VigorFetchConfig['method']): VigorFetch<T>;
|
|
36
|
+
offset(arg: RequestInit): VigorFetch<T>;
|
|
37
|
+
headers(arg: Record<string, string>): VigorFetch<T>;
|
|
38
|
+
body(arg: any): VigorFetch<T>;
|
|
39
|
+
count(arg: number): VigorFetch<T>;
|
|
40
|
+
max(arg: number): VigorFetch<T>;
|
|
41
|
+
wait(arg: number): VigorFetch<T>;
|
|
42
|
+
backoff(arg: number): VigorFetch<T>;
|
|
43
|
+
unretry(arg: number[]): VigorFetch<T>;
|
|
44
|
+
retryHeader(...arg: string[]): VigorFetch<T>;
|
|
45
|
+
original(arg: boolean): VigorFetch<T>;
|
|
46
|
+
parse(arg: keyof Response): VigorFetch<T>;
|
|
47
|
+
query(arg: Record<string, any>): VigorFetch<T>;
|
|
48
|
+
jitter(arg: number): VigorFetch<T>;
|
|
49
|
+
beforeRequest(...arg: VigorFetchConfig['beforeRequest']): VigorFetch<T>;
|
|
50
|
+
afterRequest(...arg: VigorFetchConfig['afterRequest']): VigorFetch<T>;
|
|
51
|
+
beforeResponse(...arg: VigorFetchConfig['beforeResponse']): VigorFetch<T>;
|
|
52
|
+
afterResponse(...arg: VigorFetchConfig['afterResponse']): VigorFetch<T>;
|
|
53
|
+
onError(...arg: VigorFetchConfig['onError']): VigorFetch<T>;
|
|
54
|
+
request(): Promise<T>;
|
|
55
|
+
}
|
|
56
|
+
interface VigorAllConfig {
|
|
57
|
+
limit: number;
|
|
58
|
+
jitter: number;
|
|
59
|
+
promises: Array<() => Promise<any>>;
|
|
60
|
+
}
|
|
61
|
+
declare class VigorAll {
|
|
62
|
+
private _config;
|
|
63
|
+
constructor(config?: VigorAllConfig);
|
|
64
|
+
private _next;
|
|
65
|
+
limit(arg: number): VigorAll;
|
|
66
|
+
jitter(arg: number): VigorAll;
|
|
67
|
+
promises(...args: Array<() => Promise<any>>): VigorAll;
|
|
68
|
+
request(): Promise<any[]>;
|
|
69
|
+
}
|
|
70
|
+
declare class Vigor {
|
|
71
|
+
fetch<T = any>(origin: string, config?: VigorFetchConfig): VigorFetch<T>;
|
|
72
|
+
all(config?: VigorAllConfig): VigorAll;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export { type VigorAllConfig, type VigorErrorOptions, type VigorFetchConfig, Vigor as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
interface
|
|
1
|
+
interface VigorErrorOptions {
|
|
2
|
+
url?: string | null;
|
|
3
|
+
status?: number;
|
|
4
|
+
message?: string;
|
|
5
|
+
data?: any;
|
|
6
|
+
}
|
|
7
|
+
interface VigorFetchConfig {
|
|
2
8
|
path: string;
|
|
3
|
-
method:
|
|
9
|
+
method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD" | null;
|
|
4
10
|
offset: RequestInit;
|
|
5
11
|
headers: Record<string, string>;
|
|
6
12
|
body: any;
|
|
@@ -9,34 +15,61 @@ interface VigorConfig {
|
|
|
9
15
|
wait: number;
|
|
10
16
|
backoff: number;
|
|
11
17
|
unretry: Set<number>;
|
|
18
|
+
retryHeader: string[];
|
|
12
19
|
original: boolean;
|
|
13
20
|
parse: keyof Response | null;
|
|
14
|
-
beforeRequest: (
|
|
15
|
-
|
|
16
|
-
|
|
21
|
+
beforeRequest: Array<(opt: RequestInit) => Promise<Partial<RequestInit> | void> | Partial<RequestInit> | void>;
|
|
22
|
+
afterRequest: Array<(res: Response) => Promise<Response | void> | Response | void>;
|
|
23
|
+
beforeResponse: Array<(res: Response) => Promise<Response> | Response>;
|
|
24
|
+
afterResponse: Array<(data: any) => Promise<any> | any>;
|
|
25
|
+
onError: Array<(err: any) => Promise<any> | any>;
|
|
26
|
+
query: Record<string, any>;
|
|
17
27
|
jitter: number;
|
|
18
28
|
}
|
|
19
|
-
|
|
29
|
+
declare class VigorFetch<T = any> {
|
|
20
30
|
private _origin;
|
|
21
31
|
private _config;
|
|
22
|
-
constructor(origin: string, config?:
|
|
32
|
+
constructor(origin: string, config?: VigorFetchConfig);
|
|
23
33
|
private _next;
|
|
24
|
-
path(arg: string):
|
|
25
|
-
method(arg:
|
|
26
|
-
offset(arg: RequestInit):
|
|
27
|
-
headers(arg: Record<string, string>):
|
|
28
|
-
body(arg: any):
|
|
29
|
-
count(arg: number):
|
|
30
|
-
max(arg: number):
|
|
31
|
-
wait(arg: number):
|
|
32
|
-
backoff(arg: number):
|
|
33
|
-
unretry(arg: number[]):
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
path(arg: string): VigorFetch<T>;
|
|
35
|
+
method(arg: VigorFetchConfig['method']): VigorFetch<T>;
|
|
36
|
+
offset(arg: RequestInit): VigorFetch<T>;
|
|
37
|
+
headers(arg: Record<string, string>): VigorFetch<T>;
|
|
38
|
+
body(arg: any): VigorFetch<T>;
|
|
39
|
+
count(arg: number): VigorFetch<T>;
|
|
40
|
+
max(arg: number): VigorFetch<T>;
|
|
41
|
+
wait(arg: number): VigorFetch<T>;
|
|
42
|
+
backoff(arg: number): VigorFetch<T>;
|
|
43
|
+
unretry(arg: number[]): VigorFetch<T>;
|
|
44
|
+
retryHeader(...arg: string[]): VigorFetch<T>;
|
|
45
|
+
original(arg: boolean): VigorFetch<T>;
|
|
46
|
+
parse(arg: keyof Response): VigorFetch<T>;
|
|
47
|
+
query(arg: Record<string, any>): VigorFetch<T>;
|
|
48
|
+
jitter(arg: number): VigorFetch<T>;
|
|
49
|
+
beforeRequest(...arg: VigorFetchConfig['beforeRequest']): VigorFetch<T>;
|
|
50
|
+
afterRequest(...arg: VigorFetchConfig['afterRequest']): VigorFetch<T>;
|
|
51
|
+
beforeResponse(...arg: VigorFetchConfig['beforeResponse']): VigorFetch<T>;
|
|
52
|
+
afterResponse(...arg: VigorFetchConfig['afterResponse']): VigorFetch<T>;
|
|
53
|
+
onError(...arg: VigorFetchConfig['onError']): VigorFetch<T>;
|
|
54
|
+
request(): Promise<T>;
|
|
55
|
+
}
|
|
56
|
+
interface VigorAllConfig {
|
|
57
|
+
limit: number;
|
|
58
|
+
jitter: number;
|
|
59
|
+
promises: Array<() => Promise<any>>;
|
|
60
|
+
}
|
|
61
|
+
declare class VigorAll {
|
|
62
|
+
private _config;
|
|
63
|
+
constructor(config?: VigorAllConfig);
|
|
64
|
+
private _next;
|
|
65
|
+
limit(arg: number): VigorAll;
|
|
66
|
+
jitter(arg: number): VigorAll;
|
|
67
|
+
promises(...args: Array<() => Promise<any>>): VigorAll;
|
|
68
|
+
request(): Promise<any[]>;
|
|
69
|
+
}
|
|
70
|
+
declare class Vigor {
|
|
71
|
+
fetch<T = any>(origin: string, config?: VigorFetchConfig): VigorFetch<T>;
|
|
72
|
+
all(config?: VigorAllConfig): VigorAll;
|
|
41
73
|
}
|
|
42
|
-
|
|
74
|
+
|
|
75
|
+
export { type VigorAllConfig, type VigorErrorOptions, type VigorFetchConfig, Vigor as default };
|
package/dist/index.js
CHANGED
|
@@ -1,158 +1,282 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
Object.defineProperty
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
default: () => index_default
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
var VigorError = class extends Error {
|
|
27
|
+
constructor(text, { url = null, status = 0, message, data = null }) {
|
|
28
|
+
super(text);
|
|
29
|
+
this.name = "VigorError";
|
|
30
|
+
this.url = url;
|
|
31
|
+
this.status = status;
|
|
32
|
+
this.message = message || text;
|
|
33
|
+
this.data = data;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
var VigorFetch = class _VigorFetch {
|
|
37
|
+
constructor(origin, config) {
|
|
38
|
+
this._origin = origin;
|
|
39
|
+
this._config = config || {
|
|
40
|
+
path: "",
|
|
41
|
+
method: null,
|
|
42
|
+
offset: {},
|
|
43
|
+
headers: {},
|
|
44
|
+
body: null,
|
|
45
|
+
count: 5,
|
|
46
|
+
max: 5e3,
|
|
47
|
+
wait: 1e4,
|
|
48
|
+
backoff: 1.3,
|
|
49
|
+
unretry: /* @__PURE__ */ new Set([400, 401, 403, 404, 405, 413, 422]),
|
|
50
|
+
retryHeader: ["retry-after", "ratelimit-reset", "x-ratelimit-reset", "x-retry-after", "x-amz-retry-after", "chrome-proxy-next-link"],
|
|
51
|
+
original: false,
|
|
52
|
+
parse: null,
|
|
53
|
+
query: {},
|
|
54
|
+
jitter: 500,
|
|
55
|
+
beforeRequest: [],
|
|
56
|
+
afterRequest: [],
|
|
57
|
+
beforeResponse: [],
|
|
58
|
+
afterResponse: [],
|
|
59
|
+
onError: []
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
_next(changes) {
|
|
63
|
+
return new _VigorFetch(this._origin, { ...this._config, ...changes });
|
|
64
|
+
}
|
|
65
|
+
path(arg) {
|
|
66
|
+
return this._next({ path: arg });
|
|
67
|
+
}
|
|
68
|
+
method(arg) {
|
|
69
|
+
return this._next({ method: arg });
|
|
70
|
+
}
|
|
71
|
+
offset(arg) {
|
|
72
|
+
return this._next({ offset: arg });
|
|
73
|
+
}
|
|
74
|
+
headers(arg) {
|
|
75
|
+
return this._next({ headers: arg });
|
|
76
|
+
}
|
|
77
|
+
body(arg) {
|
|
78
|
+
return this._next({ body: arg });
|
|
79
|
+
}
|
|
80
|
+
count(arg) {
|
|
81
|
+
return this._next({ count: arg });
|
|
82
|
+
}
|
|
83
|
+
max(arg) {
|
|
84
|
+
return this._next({ max: arg });
|
|
85
|
+
}
|
|
86
|
+
wait(arg) {
|
|
87
|
+
return this._next({ wait: arg });
|
|
88
|
+
}
|
|
89
|
+
backoff(arg) {
|
|
90
|
+
return this._next({ backoff: arg });
|
|
91
|
+
}
|
|
92
|
+
unretry(arg) {
|
|
93
|
+
return this._next({ unretry: new Set(arg) });
|
|
94
|
+
}
|
|
95
|
+
retryHeader(...arg) {
|
|
96
|
+
return this._next({ retryHeader: [...this._config.retryHeader, ...arg] });
|
|
97
|
+
}
|
|
98
|
+
original(arg) {
|
|
99
|
+
return this._next({ original: arg });
|
|
100
|
+
}
|
|
101
|
+
parse(arg) {
|
|
102
|
+
return this._next({ parse: arg });
|
|
103
|
+
}
|
|
104
|
+
query(arg) {
|
|
105
|
+
return this._next({ query: { ...this._config.query, ...arg } });
|
|
106
|
+
}
|
|
107
|
+
jitter(arg) {
|
|
108
|
+
return this._next({ jitter: arg });
|
|
109
|
+
}
|
|
110
|
+
beforeRequest(...arg) {
|
|
111
|
+
return this._next({ beforeRequest: [...this._config.beforeRequest, ...arg] });
|
|
112
|
+
}
|
|
113
|
+
afterRequest(...arg) {
|
|
114
|
+
return this._next({ afterRequest: [...this._config.afterRequest, ...arg] });
|
|
115
|
+
}
|
|
116
|
+
beforeResponse(...arg) {
|
|
117
|
+
return this._next({ beforeResponse: [...this._config.beforeResponse, ...arg] });
|
|
118
|
+
}
|
|
119
|
+
afterResponse(...arg) {
|
|
120
|
+
return this._next({ afterResponse: [...this._config.afterResponse, ...arg] });
|
|
121
|
+
}
|
|
122
|
+
onError(...arg) {
|
|
123
|
+
return this._next({ onError: [...this._config.onError, ...arg] });
|
|
124
|
+
}
|
|
125
|
+
async request() {
|
|
126
|
+
const {
|
|
127
|
+
path,
|
|
128
|
+
method,
|
|
129
|
+
offset,
|
|
130
|
+
headers,
|
|
131
|
+
body,
|
|
132
|
+
query,
|
|
133
|
+
count,
|
|
134
|
+
max,
|
|
135
|
+
wait,
|
|
136
|
+
backoff,
|
|
137
|
+
unretry,
|
|
138
|
+
jitter,
|
|
139
|
+
original,
|
|
140
|
+
parse,
|
|
141
|
+
retryHeader,
|
|
142
|
+
beforeRequest,
|
|
143
|
+
afterRequest,
|
|
144
|
+
beforeResponse,
|
|
145
|
+
afterResponse,
|
|
146
|
+
onError
|
|
147
|
+
} = this._config;
|
|
148
|
+
try {
|
|
149
|
+
if (!/^(https?|data|blob|file|about):\/\//.test(this._origin)) {
|
|
150
|
+
throw new VigorError(`[vigor] ${this._origin} >> Invalid Protocol`, {
|
|
151
|
+
url: this._origin,
|
|
152
|
+
status: 0,
|
|
153
|
+
message: "Invalid Protocol"
|
|
70
154
|
});
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
155
|
+
}
|
|
156
|
+
const urlObj = new URL(path.replace(/^\//, ""), this._origin + "/");
|
|
157
|
+
Object.entries(query).forEach(([key, value]) => {
|
|
158
|
+
if (value !== null && value !== void 0) urlObj.searchParams.append(key, String(value));
|
|
159
|
+
});
|
|
160
|
+
const url = urlObj.href;
|
|
161
|
+
const isJson = Array.isArray(body) || !!body && Object.getPrototypeOf(body) === Object.prototype;
|
|
162
|
+
const waitTimeout = (time) => new Promise((resolve) => setTimeout(resolve, time));
|
|
163
|
+
let option = {
|
|
164
|
+
...offset,
|
|
165
|
+
method: method || (body ? "POST" : "GET"),
|
|
166
|
+
headers: { ...isJson && { "Content-Type": "application/json" }, ...headers },
|
|
167
|
+
...body && { body: isJson ? JSON.stringify(body) : body }
|
|
168
|
+
};
|
|
169
|
+
for (const hook of beforeRequest) {
|
|
170
|
+
const modified = await hook(option);
|
|
171
|
+
if (modified) option = { ...option, ...modified };
|
|
172
|
+
}
|
|
173
|
+
let req;
|
|
174
|
+
for (let i = 0; i < count; i++) {
|
|
175
|
+
const controller = new AbortController();
|
|
176
|
+
const abort = setTimeout(() => controller.abort(), max);
|
|
177
|
+
option.signal = controller.signal;
|
|
178
|
+
try {
|
|
179
|
+
req = await fetch(url, option);
|
|
180
|
+
for (const hook of afterRequest) {
|
|
181
|
+
req = await hook(req) || req;
|
|
182
|
+
}
|
|
183
|
+
if (req.ok) {
|
|
184
|
+
clearTimeout(abort);
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
} catch (error) {
|
|
188
|
+
clearTimeout(abort);
|
|
189
|
+
if (i === count - 1) throw new VigorError(`[vigor] ${url} >> Network Error`, { url, status: 0, message: "Network Error" });
|
|
190
|
+
} finally {
|
|
191
|
+
clearTimeout(abort);
|
|
86
192
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
clearTimeout(abort);
|
|
101
|
-
if (i === count - 1)
|
|
102
|
-
throw new VigorError(`[vigor] ${url} >> Network Error`, {
|
|
103
|
-
url, status: 0, message: "Network Error"
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
finally {
|
|
107
|
-
clearTimeout(abort);
|
|
108
|
-
}
|
|
109
|
-
if (req) {
|
|
110
|
-
const status = req.status;
|
|
111
|
-
if (unretry.has(status))
|
|
112
|
-
throw new VigorError(`[vigor] ${url} >> Unretry ${status}`, {
|
|
113
|
-
url, status, message: "Unretry", data: status
|
|
114
|
-
});
|
|
115
|
-
const basic = Math.min(Math.pow(backoff, i) * 1000, wait) + Math.random() * jitter;
|
|
116
|
-
if (status === 429) {
|
|
117
|
-
const retryHeaders = ["retry-after", "ratelimit-reset", "x-ratelimit-reset", "x-retry-after", "x-amz-retry-after", "chrome-proxy-next-link"];
|
|
118
|
-
const retryAfter = retryHeaders.map(h => req?.headers.get(h)).find(Boolean);
|
|
119
|
-
const delay = (Number(retryAfter) * 1000 || (new Date(retryAfter).getTime() - Date.now()));
|
|
120
|
-
const parsedDelay = Math.max(0, delay) + Math.random() * jitter;
|
|
121
|
-
if (parsedDelay > wait)
|
|
122
|
-
throw new VigorError(`[vigor] ${url} >> Timeouted ${parsedDelay}ms`, {
|
|
123
|
-
url, status, message: "Timeouted", data: parsedDelay
|
|
124
|
-
});
|
|
125
|
-
await waitTimeout(parsedDelay || basic);
|
|
126
|
-
}
|
|
127
|
-
else {
|
|
128
|
-
await waitTimeout(basic);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
193
|
+
if (req) {
|
|
194
|
+
const status = req.status;
|
|
195
|
+
if (unretry.has(status)) throw new VigorError(`[vigor] ${url} >> Unretry ${status}`, { url, status, message: "Unretry", data: status });
|
|
196
|
+
const basic = Math.min(Math.pow(backoff, i) * 1e3, wait) + Math.random() * jitter;
|
|
197
|
+
if (status === 429) {
|
|
198
|
+
const rHeader = retryHeader.map((h) => req?.headers.get(h)).find(Boolean);
|
|
199
|
+
const delay = rHeader ? isNaN(Number(rHeader)) ? new Date(rHeader).getTime() - Date.now() : Number(rHeader) * 1e3 : 0;
|
|
200
|
+
const parsedDelay = Math.max(0, delay) + Math.random() * jitter;
|
|
201
|
+
if (parsedDelay > wait) throw new VigorError(`[vigor] ${url} >> Timeouted ${parsedDelay}ms`, { url, status, message: "Timeouted", data: parsedDelay });
|
|
202
|
+
await waitTimeout(parsedDelay || basic);
|
|
203
|
+
} else {
|
|
204
|
+
await waitTimeout(basic);
|
|
205
|
+
}
|
|
131
206
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (/(image|video|audio|pdf)/.test(contentType))
|
|
145
|
-
return await req.blob();
|
|
146
|
-
if (/octet-stream/.test(contentType))
|
|
147
|
-
return await req.arrayBuffer();
|
|
148
|
-
if (/form-data/.test(contentType))
|
|
149
|
-
return await req.formData();
|
|
150
|
-
return await req.text();
|
|
151
|
-
})();
|
|
152
|
-
for (const hook of afterResponse) {
|
|
153
|
-
res = await hook(res);
|
|
207
|
+
}
|
|
208
|
+
if (!req) throw new Error("No response");
|
|
209
|
+
let currentReq = req;
|
|
210
|
+
for (const hook of beforeResponse) {
|
|
211
|
+
currentReq = await hook(currentReq);
|
|
212
|
+
}
|
|
213
|
+
if (!currentReq.ok) throw new VigorError(`[vigor] ${url} >> Failed`, { url, status: currentReq.status, message: "Failed" });
|
|
214
|
+
let res = await (async () => {
|
|
215
|
+
if (original) return currentReq;
|
|
216
|
+
if (parse) {
|
|
217
|
+
const target = currentReq[parse];
|
|
218
|
+
return typeof target === "function" ? await target.call(currentReq) : target;
|
|
154
219
|
}
|
|
155
|
-
|
|
220
|
+
const contentType = currentReq.headers.get("Content-Type") || "";
|
|
221
|
+
if (/json/.test(contentType)) return await currentReq.json();
|
|
222
|
+
if (/(image|video|audio|pdf)/.test(contentType)) return await currentReq.blob();
|
|
223
|
+
return await currentReq.text();
|
|
224
|
+
})();
|
|
225
|
+
for (const hook of afterResponse) {
|
|
226
|
+
res = await hook(res);
|
|
227
|
+
}
|
|
228
|
+
return res;
|
|
229
|
+
} catch (error) {
|
|
230
|
+
let currentError = error;
|
|
231
|
+
for (const hook of onError) {
|
|
232
|
+
const result = await hook(currentError);
|
|
233
|
+
if (result !== void 0 && !(result instanceof Error)) return result;
|
|
234
|
+
currentError = result || currentError;
|
|
235
|
+
}
|
|
236
|
+
throw currentError;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
var VigorAll = class _VigorAll {
|
|
241
|
+
constructor(config) {
|
|
242
|
+
this._config = config || { limit: 10, jitter: 1e3, promises: [] };
|
|
243
|
+
}
|
|
244
|
+
_next(changes) {
|
|
245
|
+
return new _VigorAll({ ...this._config, ...changes });
|
|
246
|
+
}
|
|
247
|
+
limit(arg) {
|
|
248
|
+
return this._next({ limit: arg });
|
|
249
|
+
}
|
|
250
|
+
jitter(arg) {
|
|
251
|
+
return this._next({ jitter: arg });
|
|
252
|
+
}
|
|
253
|
+
promises(...args) {
|
|
254
|
+
return this._next({ promises: [...this._config.promises, ...args] });
|
|
255
|
+
}
|
|
256
|
+
async request() {
|
|
257
|
+
const { limit, jitter, promises } = this._config;
|
|
258
|
+
const results = [];
|
|
259
|
+
const executing = /* @__PURE__ */ new Set();
|
|
260
|
+
for (const task of promises) {
|
|
261
|
+
const p = Promise.resolve().then(() => new Promise((res) => setTimeout(res, Math.random() * jitter))).then(() => task());
|
|
262
|
+
results.push(p);
|
|
263
|
+
executing.add(p);
|
|
264
|
+
p.finally(() => executing.delete(p));
|
|
265
|
+
if (executing.size >= limit) await Promise.race(executing);
|
|
156
266
|
}
|
|
157
|
-
|
|
158
|
-
|
|
267
|
+
const ready = await Promise.allSettled(results);
|
|
268
|
+
return ready.map((i) => {
|
|
269
|
+
if (i.status === "fulfilled") return i.value;
|
|
270
|
+
return i.reason instanceof VigorError ? i.reason : new VigorError(i.reason?.message || "Unknown", { message: i.reason?.message || "Unknown" });
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
var Vigor = class {
|
|
275
|
+
fetch(origin, config) {
|
|
276
|
+
return new VigorFetch(origin, config);
|
|
277
|
+
}
|
|
278
|
+
all(config) {
|
|
279
|
+
return new VigorAll(config);
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
var index_default = Vigor;
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var VigorError = class extends Error {
|
|
3
|
+
constructor(text, { url = null, status = 0, message, data = null }) {
|
|
4
|
+
super(text);
|
|
5
|
+
this.name = "VigorError";
|
|
6
|
+
this.url = url;
|
|
7
|
+
this.status = status;
|
|
8
|
+
this.message = message || text;
|
|
9
|
+
this.data = data;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
var VigorFetch = class _VigorFetch {
|
|
13
|
+
constructor(origin, config) {
|
|
14
|
+
this._origin = origin;
|
|
15
|
+
this._config = config || {
|
|
16
|
+
path: "",
|
|
17
|
+
method: null,
|
|
18
|
+
offset: {},
|
|
19
|
+
headers: {},
|
|
20
|
+
body: null,
|
|
21
|
+
count: 5,
|
|
22
|
+
max: 5e3,
|
|
23
|
+
wait: 1e4,
|
|
24
|
+
backoff: 1.3,
|
|
25
|
+
unretry: /* @__PURE__ */ new Set([400, 401, 403, 404, 405, 413, 422]),
|
|
26
|
+
retryHeader: ["retry-after", "ratelimit-reset", "x-ratelimit-reset", "x-retry-after", "x-amz-retry-after", "chrome-proxy-next-link"],
|
|
27
|
+
original: false,
|
|
28
|
+
parse: null,
|
|
29
|
+
query: {},
|
|
30
|
+
jitter: 500,
|
|
31
|
+
beforeRequest: [],
|
|
32
|
+
afterRequest: [],
|
|
33
|
+
beforeResponse: [],
|
|
34
|
+
afterResponse: [],
|
|
35
|
+
onError: []
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
_next(changes) {
|
|
39
|
+
return new _VigorFetch(this._origin, { ...this._config, ...changes });
|
|
40
|
+
}
|
|
41
|
+
path(arg) {
|
|
42
|
+
return this._next({ path: arg });
|
|
43
|
+
}
|
|
44
|
+
method(arg) {
|
|
45
|
+
return this._next({ method: arg });
|
|
46
|
+
}
|
|
47
|
+
offset(arg) {
|
|
48
|
+
return this._next({ offset: arg });
|
|
49
|
+
}
|
|
50
|
+
headers(arg) {
|
|
51
|
+
return this._next({ headers: arg });
|
|
52
|
+
}
|
|
53
|
+
body(arg) {
|
|
54
|
+
return this._next({ body: arg });
|
|
55
|
+
}
|
|
56
|
+
count(arg) {
|
|
57
|
+
return this._next({ count: arg });
|
|
58
|
+
}
|
|
59
|
+
max(arg) {
|
|
60
|
+
return this._next({ max: arg });
|
|
61
|
+
}
|
|
62
|
+
wait(arg) {
|
|
63
|
+
return this._next({ wait: arg });
|
|
64
|
+
}
|
|
65
|
+
backoff(arg) {
|
|
66
|
+
return this._next({ backoff: arg });
|
|
67
|
+
}
|
|
68
|
+
unretry(arg) {
|
|
69
|
+
return this._next({ unretry: new Set(arg) });
|
|
70
|
+
}
|
|
71
|
+
retryHeader(...arg) {
|
|
72
|
+
return this._next({ retryHeader: [...this._config.retryHeader, ...arg] });
|
|
73
|
+
}
|
|
74
|
+
original(arg) {
|
|
75
|
+
return this._next({ original: arg });
|
|
76
|
+
}
|
|
77
|
+
parse(arg) {
|
|
78
|
+
return this._next({ parse: arg });
|
|
79
|
+
}
|
|
80
|
+
query(arg) {
|
|
81
|
+
return this._next({ query: { ...this._config.query, ...arg } });
|
|
82
|
+
}
|
|
83
|
+
jitter(arg) {
|
|
84
|
+
return this._next({ jitter: arg });
|
|
85
|
+
}
|
|
86
|
+
beforeRequest(...arg) {
|
|
87
|
+
return this._next({ beforeRequest: [...this._config.beforeRequest, ...arg] });
|
|
88
|
+
}
|
|
89
|
+
afterRequest(...arg) {
|
|
90
|
+
return this._next({ afterRequest: [...this._config.afterRequest, ...arg] });
|
|
91
|
+
}
|
|
92
|
+
beforeResponse(...arg) {
|
|
93
|
+
return this._next({ beforeResponse: [...this._config.beforeResponse, ...arg] });
|
|
94
|
+
}
|
|
95
|
+
afterResponse(...arg) {
|
|
96
|
+
return this._next({ afterResponse: [...this._config.afterResponse, ...arg] });
|
|
97
|
+
}
|
|
98
|
+
onError(...arg) {
|
|
99
|
+
return this._next({ onError: [...this._config.onError, ...arg] });
|
|
100
|
+
}
|
|
101
|
+
async request() {
|
|
102
|
+
const {
|
|
103
|
+
path,
|
|
104
|
+
method,
|
|
105
|
+
offset,
|
|
106
|
+
headers,
|
|
107
|
+
body,
|
|
108
|
+
query,
|
|
109
|
+
count,
|
|
110
|
+
max,
|
|
111
|
+
wait,
|
|
112
|
+
backoff,
|
|
113
|
+
unretry,
|
|
114
|
+
jitter,
|
|
115
|
+
original,
|
|
116
|
+
parse,
|
|
117
|
+
retryHeader,
|
|
118
|
+
beforeRequest,
|
|
119
|
+
afterRequest,
|
|
120
|
+
beforeResponse,
|
|
121
|
+
afterResponse,
|
|
122
|
+
onError
|
|
123
|
+
} = this._config;
|
|
124
|
+
try {
|
|
125
|
+
if (!/^(https?|data|blob|file|about):\/\//.test(this._origin)) {
|
|
126
|
+
throw new VigorError(`[vigor] ${this._origin} >> Invalid Protocol`, {
|
|
127
|
+
url: this._origin,
|
|
128
|
+
status: 0,
|
|
129
|
+
message: "Invalid Protocol"
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
const urlObj = new URL(path.replace(/^\//, ""), this._origin + "/");
|
|
133
|
+
Object.entries(query).forEach(([key, value]) => {
|
|
134
|
+
if (value !== null && value !== void 0) urlObj.searchParams.append(key, String(value));
|
|
135
|
+
});
|
|
136
|
+
const url = urlObj.href;
|
|
137
|
+
const isJson = Array.isArray(body) || !!body && Object.getPrototypeOf(body) === Object.prototype;
|
|
138
|
+
const waitTimeout = (time) => new Promise((resolve) => setTimeout(resolve, time));
|
|
139
|
+
let option = {
|
|
140
|
+
...offset,
|
|
141
|
+
method: method || (body ? "POST" : "GET"),
|
|
142
|
+
headers: { ...isJson && { "Content-Type": "application/json" }, ...headers },
|
|
143
|
+
...body && { body: isJson ? JSON.stringify(body) : body }
|
|
144
|
+
};
|
|
145
|
+
for (const hook of beforeRequest) {
|
|
146
|
+
const modified = await hook(option);
|
|
147
|
+
if (modified) option = { ...option, ...modified };
|
|
148
|
+
}
|
|
149
|
+
let req;
|
|
150
|
+
for (let i = 0; i < count; i++) {
|
|
151
|
+
const controller = new AbortController();
|
|
152
|
+
const abort = setTimeout(() => controller.abort(), max);
|
|
153
|
+
option.signal = controller.signal;
|
|
154
|
+
try {
|
|
155
|
+
req = await fetch(url, option);
|
|
156
|
+
for (const hook of afterRequest) {
|
|
157
|
+
req = await hook(req) || req;
|
|
158
|
+
}
|
|
159
|
+
if (req.ok) {
|
|
160
|
+
clearTimeout(abort);
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
} catch (error) {
|
|
164
|
+
clearTimeout(abort);
|
|
165
|
+
if (i === count - 1) throw new VigorError(`[vigor] ${url} >> Network Error`, { url, status: 0, message: "Network Error" });
|
|
166
|
+
} finally {
|
|
167
|
+
clearTimeout(abort);
|
|
168
|
+
}
|
|
169
|
+
if (req) {
|
|
170
|
+
const status = req.status;
|
|
171
|
+
if (unretry.has(status)) throw new VigorError(`[vigor] ${url} >> Unretry ${status}`, { url, status, message: "Unretry", data: status });
|
|
172
|
+
const basic = Math.min(Math.pow(backoff, i) * 1e3, wait) + Math.random() * jitter;
|
|
173
|
+
if (status === 429) {
|
|
174
|
+
const rHeader = retryHeader.map((h) => req?.headers.get(h)).find(Boolean);
|
|
175
|
+
const delay = rHeader ? isNaN(Number(rHeader)) ? new Date(rHeader).getTime() - Date.now() : Number(rHeader) * 1e3 : 0;
|
|
176
|
+
const parsedDelay = Math.max(0, delay) + Math.random() * jitter;
|
|
177
|
+
if (parsedDelay > wait) throw new VigorError(`[vigor] ${url} >> Timeouted ${parsedDelay}ms`, { url, status, message: "Timeouted", data: parsedDelay });
|
|
178
|
+
await waitTimeout(parsedDelay || basic);
|
|
179
|
+
} else {
|
|
180
|
+
await waitTimeout(basic);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (!req) throw new Error("No response");
|
|
185
|
+
let currentReq = req;
|
|
186
|
+
for (const hook of beforeResponse) {
|
|
187
|
+
currentReq = await hook(currentReq);
|
|
188
|
+
}
|
|
189
|
+
if (!currentReq.ok) throw new VigorError(`[vigor] ${url} >> Failed`, { url, status: currentReq.status, message: "Failed" });
|
|
190
|
+
let res = await (async () => {
|
|
191
|
+
if (original) return currentReq;
|
|
192
|
+
if (parse) {
|
|
193
|
+
const target = currentReq[parse];
|
|
194
|
+
return typeof target === "function" ? await target.call(currentReq) : target;
|
|
195
|
+
}
|
|
196
|
+
const contentType = currentReq.headers.get("Content-Type") || "";
|
|
197
|
+
if (/json/.test(contentType)) return await currentReq.json();
|
|
198
|
+
if (/(image|video|audio|pdf)/.test(contentType)) return await currentReq.blob();
|
|
199
|
+
return await currentReq.text();
|
|
200
|
+
})();
|
|
201
|
+
for (const hook of afterResponse) {
|
|
202
|
+
res = await hook(res);
|
|
203
|
+
}
|
|
204
|
+
return res;
|
|
205
|
+
} catch (error) {
|
|
206
|
+
let currentError = error;
|
|
207
|
+
for (const hook of onError) {
|
|
208
|
+
const result = await hook(currentError);
|
|
209
|
+
if (result !== void 0 && !(result instanceof Error)) return result;
|
|
210
|
+
currentError = result || currentError;
|
|
211
|
+
}
|
|
212
|
+
throw currentError;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
var VigorAll = class _VigorAll {
|
|
217
|
+
constructor(config) {
|
|
218
|
+
this._config = config || { limit: 10, jitter: 1e3, promises: [] };
|
|
219
|
+
}
|
|
220
|
+
_next(changes) {
|
|
221
|
+
return new _VigorAll({ ...this._config, ...changes });
|
|
222
|
+
}
|
|
223
|
+
limit(arg) {
|
|
224
|
+
return this._next({ limit: arg });
|
|
225
|
+
}
|
|
226
|
+
jitter(arg) {
|
|
227
|
+
return this._next({ jitter: arg });
|
|
228
|
+
}
|
|
229
|
+
promises(...args) {
|
|
230
|
+
return this._next({ promises: [...this._config.promises, ...args] });
|
|
231
|
+
}
|
|
232
|
+
async request() {
|
|
233
|
+
const { limit, jitter, promises } = this._config;
|
|
234
|
+
const results = [];
|
|
235
|
+
const executing = /* @__PURE__ */ new Set();
|
|
236
|
+
for (const task of promises) {
|
|
237
|
+
const p = Promise.resolve().then(() => new Promise((res) => setTimeout(res, Math.random() * jitter))).then(() => task());
|
|
238
|
+
results.push(p);
|
|
239
|
+
executing.add(p);
|
|
240
|
+
p.finally(() => executing.delete(p));
|
|
241
|
+
if (executing.size >= limit) await Promise.race(executing);
|
|
242
|
+
}
|
|
243
|
+
const ready = await Promise.allSettled(results);
|
|
244
|
+
return ready.map((i) => {
|
|
245
|
+
if (i.status === "fulfilled") return i.value;
|
|
246
|
+
return i.reason instanceof VigorError ? i.reason : new VigorError(i.reason?.message || "Unknown", { message: i.reason?.message || "Unknown" });
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
var Vigor = class {
|
|
251
|
+
fetch(origin, config) {
|
|
252
|
+
return new VigorFetch(origin, config);
|
|
253
|
+
}
|
|
254
|
+
all(config) {
|
|
255
|
+
return new VigorAll(config);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
var index_default = Vigor;
|
|
259
|
+
export {
|
|
260
|
+
index_default as default
|
|
261
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vigor-fetch",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Smart, zero-dependency HTTP client with self-healing retries for rate-limited servers.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -9,20 +9,22 @@
|
|
|
9
9
|
"dist"
|
|
10
10
|
],
|
|
11
11
|
"scripts": {
|
|
12
|
-
"build": "
|
|
13
|
-
"
|
|
12
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
13
|
+
"dev": "tsup src/index.ts --format cjs,esm --watch --dts"
|
|
14
14
|
},
|
|
15
15
|
"keywords": [
|
|
16
16
|
"fetch",
|
|
17
|
+
"PromiseAll",
|
|
17
18
|
"retry",
|
|
18
19
|
"backoff",
|
|
19
20
|
"ratelimit",
|
|
20
21
|
"typescript",
|
|
21
22
|
"zero-dependency"
|
|
22
23
|
],
|
|
23
|
-
"author": "
|
|
24
|
+
"author": "Uav1010",
|
|
24
25
|
"license": "MIT",
|
|
25
26
|
"devDependencies": {
|
|
27
|
+
"tsup": "^8.5.1",
|
|
26
28
|
"typescript": "^5.9.3"
|
|
27
29
|
},
|
|
28
30
|
"engines": {
|