vigor-fetch 1.0.11 → 1.0.12
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/dist/index.d.ts +103 -58
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +472 -155
- package/dist/index.mjs +460 -155
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,199 +1,516 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
3
5
|
class VigorError extends Error {
|
|
4
|
-
constructor(text,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
this.
|
|
8
|
-
this.status = status;
|
|
9
|
-
this.message = message || text;
|
|
6
|
+
constructor(text, options) {
|
|
7
|
+
const { type, data, status, response, message, origin } = options;
|
|
8
|
+
super(message || `[VigorError] ${text}`);
|
|
9
|
+
this.name = this.constructor.name;
|
|
10
10
|
this.data = data;
|
|
11
|
+
this.type = type;
|
|
12
|
+
this.status = status;
|
|
13
|
+
this.response = response;
|
|
14
|
+
this.origin = origin;
|
|
15
|
+
if (Error.captureStackTrace) {
|
|
16
|
+
Error.captureStackTrace(this, this.constructor);
|
|
17
|
+
}
|
|
11
18
|
}
|
|
12
19
|
}
|
|
13
|
-
class
|
|
14
|
-
constructor(
|
|
15
|
-
|
|
16
|
-
this.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
class VigorRetryError extends VigorError {
|
|
21
|
+
constructor(text, options) {
|
|
22
|
+
super(text, options);
|
|
23
|
+
this.message = options.message || `[VigorRetryError] ${text}`;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
class VigorParseError extends VigorError {
|
|
27
|
+
constructor(text, options) {
|
|
28
|
+
super(text, options);
|
|
29
|
+
this.message = options.message || `[VigorParseError] ${text}`;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
class VigorFetchError extends VigorError {
|
|
33
|
+
constructor(text, options) {
|
|
34
|
+
super(text, options);
|
|
35
|
+
this.message = options.message || `[VigorFetchError] ${text}`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
class VigorAllError extends VigorError {
|
|
39
|
+
constructor(text, options) {
|
|
40
|
+
super(text, options);
|
|
41
|
+
this.message = options.message || `[VigorFetchError] ${text}`;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* VigorRetry
|
|
46
|
+
*/
|
|
47
|
+
class VigorRetry {
|
|
48
|
+
constructor(target, args = [], config = {}) {
|
|
49
|
+
this._target = target;
|
|
50
|
+
this._args = args;
|
|
51
|
+
this._config = {
|
|
52
|
+
retry: {
|
|
53
|
+
count: 5, max: 10000, backoff: 1.3, baseDelay: 1000, jitter: 500
|
|
54
|
+
},
|
|
55
|
+
interceptors: {
|
|
56
|
+
before: [], after: [], onRetry: [], onError: []
|
|
57
|
+
},
|
|
58
|
+
...config
|
|
23
59
|
};
|
|
24
60
|
}
|
|
25
61
|
_next(changes) {
|
|
26
|
-
return new
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
backoff(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
beforeRequest(...arg) { return this._next({ beforeRequest: [...this._config.beforeRequest, ...arg] }); }
|
|
44
|
-
afterRequest(...arg) { return this._next({ afterRequest: [...this._config.afterRequest, ...arg] }); }
|
|
45
|
-
beforeResponse(...arg) { return this._next({ beforeResponse: [...this._config.beforeResponse, ...arg] }); }
|
|
46
|
-
afterResponse(...arg) { return this._next({ afterResponse: [...this._config.afterResponse, ...arg] }); }
|
|
47
|
-
onError(...arg) { return this._next({ onError: [...this._config.onError, ...arg] }); }
|
|
62
|
+
return new this.constructor(this._target, this._args, {
|
|
63
|
+
...this._config,
|
|
64
|
+
...changes,
|
|
65
|
+
retry: { ...this._config.retry, ...(changes.retry || {}) },
|
|
66
|
+
interceptors: { ...this._config.interceptors, ...(changes.interceptors || {}) }
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
args(...args) { return new this.constructor(this._target, args, this._config); }
|
|
70
|
+
count(int) { return this._next({ retry: { count: int } }); }
|
|
71
|
+
max(ms) { return this._next({ retry: { max: ms } }); }
|
|
72
|
+
backoff(ms) { return this._next({ retry: { backoff: ms } }); }
|
|
73
|
+
baseDelay(ms) { return this._next({ retry: { baseDelay: ms } }); }
|
|
74
|
+
jitter(ms) { return this._next({ retry: { jitter: ms } }); }
|
|
75
|
+
before(...func) { return this._next({ interceptors: { before: [...this._config.interceptors.before, ...func.flat()] } }); }
|
|
76
|
+
onRetry(...func) { return this._next({ interceptors: { onRetry: [...this._config.interceptors.onRetry, ...func.flat()] } }); }
|
|
77
|
+
after(...func) { return this._next({ interceptors: { after: [...this._config.interceptors.after, ...func.flat()] } }); }
|
|
78
|
+
onError(...func) { return this._next({ interceptors: { onError: [...this._config.interceptors.onError, ...func.flat()] } }); }
|
|
48
79
|
async request() {
|
|
49
|
-
const
|
|
80
|
+
const [target, args, config] = [this._target, this._args, this._config];
|
|
81
|
+
const { retry: { count, max, backoff, baseDelay, jitter }, interceptors: { before, after, onRetry, onError } } = config;
|
|
82
|
+
let ctx = { target, args, attempt: 0, result: null, error: null, try: true, retry: true, max, backoff, jitter, wait: 0, baseDelay };
|
|
50
83
|
try {
|
|
51
|
-
if (
|
|
52
|
-
throw new
|
|
53
|
-
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
const originBase = this._origin.endsWith('/') ? this._origin : this._origin + '/';
|
|
57
|
-
const cleanPath = path.replace(/^\//, "");
|
|
58
|
-
const urlObj = cleanPath
|
|
59
|
-
? new URL(cleanPath, originBase)
|
|
60
|
-
: new URL(this._origin);
|
|
61
|
-
Object.entries(query).forEach(([key, value]) => {
|
|
62
|
-
if (value !== null && value !== undefined)
|
|
63
|
-
urlObj.searchParams.append(key, String(value));
|
|
64
|
-
});
|
|
65
|
-
const url = urlObj.href;
|
|
66
|
-
const isJson = Array.isArray(body) || (!!body && Object.getPrototypeOf(body) === Object.prototype);
|
|
67
|
-
const waitTimeout = (time) => new Promise(resolve => setTimeout(resolve, time));
|
|
68
|
-
let option = {
|
|
69
|
-
...offset,
|
|
70
|
-
method: method || (body ? "POST" : "GET"),
|
|
71
|
-
headers: { ...(isJson && { "Content-Type": "application/json" }), ...headers },
|
|
72
|
-
...(body && { body: isJson ? JSON.stringify(body) : body }),
|
|
73
|
-
};
|
|
74
|
-
for (const hook of beforeRequest) {
|
|
75
|
-
const modified = await hook(option);
|
|
76
|
-
if (modified)
|
|
77
|
-
option = { ...option, ...modified };
|
|
78
|
-
}
|
|
79
|
-
let req;
|
|
84
|
+
if (typeof target !== 'function')
|
|
85
|
+
throw new VigorRetryError('target is not a function', { type: "not a function", data: "target" });
|
|
86
|
+
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
80
87
|
for (let i = 0; i < count; i++) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
88
|
+
ctx.attempt = i + 1;
|
|
89
|
+
ctx.error = null;
|
|
90
|
+
ctx.result = null;
|
|
91
|
+
ctx.retry ?? (ctx.retry = true);
|
|
92
|
+
for (const func of before) {
|
|
93
|
+
if (typeof func !== 'function')
|
|
94
|
+
throw new VigorRetryError('Interceptor<before> is not a function', { type: "not a function", data: "before" });
|
|
95
|
+
const next = await func(ctx, ctx.args);
|
|
96
|
+
if (next !== undefined && typeof next === 'object' && !Array.isArray(next))
|
|
97
|
+
ctx = { ...ctx, ...next };
|
|
98
|
+
}
|
|
99
|
+
if (!ctx.try)
|
|
100
|
+
break;
|
|
84
101
|
try {
|
|
85
|
-
|
|
86
|
-
for (const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
102
|
+
ctx.result = await ctx.target(...ctx.args);
|
|
103
|
+
for (const func of after) {
|
|
104
|
+
if (typeof func !== 'function')
|
|
105
|
+
throw new VigorRetryError('Interceptor<after> is not a function', { type: "not a function", data: "after" });
|
|
106
|
+
const next = await func(ctx, ctx.result);
|
|
107
|
+
if (next !== undefined && typeof next === 'object' && !Array.isArray(next))
|
|
108
|
+
ctx = { ...ctx, ...next };
|
|
92
109
|
}
|
|
110
|
+
if (ctx.error instanceof Error)
|
|
111
|
+
throw ctx.error;
|
|
112
|
+
if (ctx.result instanceof Error)
|
|
113
|
+
throw ctx.result;
|
|
114
|
+
return ctx.result;
|
|
93
115
|
}
|
|
94
116
|
catch (error) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const status = req.status;
|
|
104
|
-
if (unretry.has(status))
|
|
105
|
-
throw new VigorError(`[vigor] ${url} >> Unretry ${status}`, { url, status, message: "Unretry", data: status });
|
|
106
|
-
const basic = Math.min(Math.pow(backoff, i) * 1000, wait) + Math.random() * jitter;
|
|
107
|
-
if (status === 429) {
|
|
108
|
-
const rHeader = retryHeader.map(h => req?.headers.get(h)).find(Boolean);
|
|
109
|
-
const delay = rHeader ? (isNaN(Number(rHeader)) ? new Date(rHeader).getTime() - Date.now() : Number(rHeader) * 1000) : 0;
|
|
110
|
-
const parsedDelay = Math.max(0, delay) + Math.random() * jitter;
|
|
111
|
-
if (parsedDelay > wait)
|
|
112
|
-
throw new VigorError(`[vigor] ${url} >> Timeouted ${parsedDelay}ms`, { url, status, message: "Timeouted", data: parsedDelay });
|
|
113
|
-
await waitTimeout(parsedDelay || basic);
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
await waitTimeout(basic);
|
|
117
|
+
ctx.error = error;
|
|
118
|
+
ctx.wait = Math.min(Math.pow(ctx.backoff, ctx.attempt - 1) * ctx.baseDelay, max) + ctx.jitter;
|
|
119
|
+
for (const func of onRetry) {
|
|
120
|
+
if (typeof func !== 'function')
|
|
121
|
+
throw new VigorRetryError('Interceptor<onRetry> is not a function', { type: "not a function", data: "retry" });
|
|
122
|
+
const next = await func(ctx, ctx.error);
|
|
123
|
+
if (next !== undefined && typeof next === 'object' && !Array.isArray(next))
|
|
124
|
+
ctx = { ...ctx, ...next };
|
|
117
125
|
}
|
|
126
|
+
if (!ctx.retry)
|
|
127
|
+
break;
|
|
128
|
+
await sleep(ctx.wait);
|
|
118
129
|
}
|
|
119
130
|
}
|
|
120
|
-
if (
|
|
121
|
-
throw
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
131
|
+
if (ctx.error instanceof Error)
|
|
132
|
+
throw ctx.error;
|
|
133
|
+
if (ctx.result instanceof Error)
|
|
134
|
+
throw ctx.result;
|
|
135
|
+
}
|
|
136
|
+
catch (mainError) {
|
|
137
|
+
ctx.mainError = mainError;
|
|
138
|
+
for (const func of onError) {
|
|
139
|
+
if (typeof func !== 'function')
|
|
140
|
+
throw new VigorRetryError('Interceptor<onError> is not a function', { type: "not a function", data: "onError" });
|
|
141
|
+
const next = await func(ctx, ctx.mainError);
|
|
142
|
+
if (next !== undefined && typeof next === 'object' && !Array.isArray(next))
|
|
143
|
+
ctx = { ...ctx, ...next };
|
|
144
|
+
}
|
|
145
|
+
if (ctx.mainError instanceof Error)
|
|
146
|
+
throw ctx.mainError;
|
|
147
|
+
return ctx.mainError;
|
|
148
|
+
}
|
|
149
|
+
return ctx.result;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* VigorParse
|
|
154
|
+
*/
|
|
155
|
+
class VigorParse {
|
|
156
|
+
constructor(response, config = {}) {
|
|
157
|
+
this._response = response;
|
|
158
|
+
this._config = {
|
|
159
|
+
settings: { original: false, parse: null },
|
|
160
|
+
interceptors: { before: [], after: [], onError: [] },
|
|
161
|
+
...config,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
_next(changes) {
|
|
165
|
+
return new this.constructor(this._response, {
|
|
166
|
+
...this._config,
|
|
167
|
+
...changes,
|
|
168
|
+
settings: { ...this._config.settings, ...(changes.settings || {}) },
|
|
169
|
+
interceptors: { ...this._config.interceptors, ...(changes.interceptors || {}) }
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
original(bool) { return this._next({ settings: { original: bool } }); }
|
|
173
|
+
type(str) { return this._next({ settings: { parse: str } }); }
|
|
174
|
+
before(...func) { return this._next({ interceptors: { before: [...this._config.interceptors.before, ...func.flat()] } }); }
|
|
175
|
+
after(...func) { return this._next({ interceptors: { after: [...this._config.interceptors.after, ...func.flat()] } }); }
|
|
176
|
+
onError(...func) { return this._next({ interceptors: { onError: [...this._config.interceptors.onError, ...func.flat()] } }); }
|
|
177
|
+
async request() {
|
|
178
|
+
const { settings: { original, parse }, interceptors: { before, after, onError } } = this._config;
|
|
179
|
+
let ctx = { original, parse, result: null, response: this._response };
|
|
180
|
+
try {
|
|
181
|
+
for (const func of before) {
|
|
182
|
+
if (typeof func !== 'function')
|
|
183
|
+
throw new VigorParseError('Interceptor<before> is not a function', { type: "not a function", data: "before" });
|
|
184
|
+
const next = await func(ctx, ctx.response);
|
|
185
|
+
if (next !== undefined && typeof next === 'object' && !Array.isArray(next))
|
|
186
|
+
ctx = { ...ctx, ...next };
|
|
125
187
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return
|
|
188
|
+
ctx.result = await (async (response) => {
|
|
189
|
+
if (ctx.original)
|
|
190
|
+
return response;
|
|
191
|
+
if (ctx.parse) {
|
|
192
|
+
const method = response[ctx.parse];
|
|
193
|
+
if (!method || typeof method !== 'function')
|
|
194
|
+
throw new VigorParseError(`Invalid method such as ${ctx.parse}`, { type: "Invalid method", data: ctx.parse });
|
|
195
|
+
return await method.call(response);
|
|
134
196
|
}
|
|
135
|
-
const contentType =
|
|
197
|
+
const contentType = response.headers.get("Content-Type") || "";
|
|
136
198
|
if (/json/.test(contentType))
|
|
137
|
-
return await
|
|
199
|
+
return await response.json();
|
|
200
|
+
if (/multipart\/form-data/.test(contentType))
|
|
201
|
+
return await response.formData();
|
|
202
|
+
if (/octet-stream/.test(contentType))
|
|
203
|
+
return await response.arrayBuffer();
|
|
138
204
|
if (/(image|video|audio|pdf)/.test(contentType))
|
|
139
|
-
return await
|
|
140
|
-
return await
|
|
141
|
-
})();
|
|
142
|
-
for (const
|
|
143
|
-
|
|
205
|
+
return await response.blob();
|
|
206
|
+
return await response.text();
|
|
207
|
+
})(ctx.response);
|
|
208
|
+
for (const func of after) {
|
|
209
|
+
if (typeof func !== 'function')
|
|
210
|
+
throw new VigorParseError('Interceptor<after> is not a function', { type: "not a function", data: "after" });
|
|
211
|
+
const next = await func(ctx, ctx.result);
|
|
212
|
+
if (next !== undefined && typeof next === 'object' && !Array.isArray(next))
|
|
213
|
+
ctx = { ...ctx, ...next };
|
|
144
214
|
}
|
|
145
|
-
|
|
215
|
+
if (ctx.result instanceof Error)
|
|
216
|
+
throw ctx.result;
|
|
217
|
+
return ctx.result;
|
|
146
218
|
}
|
|
147
|
-
catch (
|
|
148
|
-
|
|
149
|
-
for (const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
219
|
+
catch (mainError) {
|
|
220
|
+
ctx.mainError = mainError;
|
|
221
|
+
for (const func of onError) {
|
|
222
|
+
if (typeof func !== 'function')
|
|
223
|
+
throw new VigorParseError('Interceptor<onError> is not a function', { type: "not a function", data: "onError" });
|
|
224
|
+
const next = await func(ctx, ctx.mainError);
|
|
225
|
+
if (next !== undefined && typeof next === 'object' && !Array.isArray(next))
|
|
226
|
+
ctx = { ...ctx, ...next };
|
|
154
227
|
}
|
|
155
|
-
|
|
228
|
+
if (ctx.mainError instanceof Error)
|
|
229
|
+
throw ctx.mainError;
|
|
230
|
+
return ctx.mainError;
|
|
156
231
|
}
|
|
157
232
|
}
|
|
158
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* VigorFetch
|
|
236
|
+
*/
|
|
237
|
+
class VigorFetch {
|
|
238
|
+
constructor(origin = "", config = {}) {
|
|
239
|
+
this._config = {
|
|
240
|
+
request: {
|
|
241
|
+
origin, path: "", query: {},
|
|
242
|
+
method: "", headers: {}, body: null, offset: {}
|
|
243
|
+
},
|
|
244
|
+
retry: {
|
|
245
|
+
limit: 10000,
|
|
246
|
+
retryHeaders: ["retry-after", "ratelimit-reset", "x-ratelimit-reset", "x-retry-after", "x-amz-retry-after", "chrome-proxy-next-link"],
|
|
247
|
+
unretry: new Set([400, 401, 403, 404, 406, 409, 410, 411, 413, 414, 415, 422]),
|
|
248
|
+
},
|
|
249
|
+
response: { retryConfig: undefined, parseConfig: undefined },
|
|
250
|
+
interceptors: { before: [], after: [], onError: [], result: [] },
|
|
251
|
+
...config
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
_next(changes) {
|
|
255
|
+
return new this.constructor(this._config.request.origin, {
|
|
256
|
+
...this._config,
|
|
257
|
+
...changes,
|
|
258
|
+
request: { ...this._config.request, ...(changes.request || {}) },
|
|
259
|
+
retry: { ...this._config.retry, ...(changes.retry || {}) },
|
|
260
|
+
interceptors: { ...this._config.interceptors, ...(changes.interceptors || {}) }
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
origin(str) { return this._next({ request: { origin: str } }); }
|
|
264
|
+
path(str) { return this._next({ request: { path: str } }); }
|
|
265
|
+
query(obj) { return this._next({ request: { query: obj } }); }
|
|
266
|
+
method(str) { return this._next({ request: { method: str } }); }
|
|
267
|
+
headers(obj) { return this._next({ request: { headers: obj } }); }
|
|
268
|
+
body(obj) { return this._next({ request: { body: obj } }); }
|
|
269
|
+
offset(obj) { return this._next({ request: { offset: obj } }); }
|
|
270
|
+
maxDelay(ms) { return this._next({ retry: { maxDelay: ms } }); }
|
|
271
|
+
retryHeaders(...str) { return this._next({ retry: { retryHeaders: [...this._config.retry.retryHeaders, ...str.flat()] } }); }
|
|
272
|
+
unretry(...int) { return this._next({ retry: { unretry: new Set(int.flat()) } }); }
|
|
273
|
+
before(...func) { return this._next({ interceptors: { before: [...this._config.interceptors.before, ...func.flat()] } }); }
|
|
274
|
+
after(...func) { return this._next({ interceptors: { after: [...this._config.interceptors.after, ...func.flat()] } }); }
|
|
275
|
+
result(...func) { return this._next({ interceptors: { result: [...this._config.interceptors.result, ...func.flat()] } }); }
|
|
276
|
+
onError(...func) { return this._next({ interceptors: { onError: [...this._config.interceptors.onError, ...func.flat()] } }); }
|
|
277
|
+
retryConfig(func) {
|
|
278
|
+
if (typeof func !== 'function')
|
|
279
|
+
throw new VigorFetchError("retryConfig is not a function", { type: "not a function", data: "retryConfig" });
|
|
280
|
+
const dummyRetry = func(new VigorRetry(async () => { }));
|
|
281
|
+
return this._next({ retry: { retryConfig: dummyRetry['_config'] } });
|
|
282
|
+
}
|
|
283
|
+
parseConfig(func) {
|
|
284
|
+
if (typeof func !== 'function')
|
|
285
|
+
throw new VigorFetchError("parseConfig is not a function", { type: "not a function", data: "parseConfig" });
|
|
286
|
+
const dummyParse = func(new VigorParse(null));
|
|
287
|
+
return this._next({ response: { parseConfig: dummyParse['_config'] } });
|
|
288
|
+
}
|
|
289
|
+
async request() {
|
|
290
|
+
const { request: { origin, path, query, method, headers, body, offset }, retry: { limit, retryHeaders, unretry }, interceptors: { before, after, onError, result }, response: { retryConfig, parseConfig } } = this._config;
|
|
291
|
+
let ctx = { option: null, result: null, path, origin };
|
|
292
|
+
try {
|
|
293
|
+
if (!/^(https?|data|blob|file|about):\/\//.test(origin))
|
|
294
|
+
throw new VigorFetchError(`${origin} Invalid Protocol`, { type: "Invalid Protocol", data: origin, origin: origin, status: 0 });
|
|
295
|
+
const isJson = Array.isArray(body) || (!!body && Object.getPrototypeOf(body) === Object.prototype);
|
|
296
|
+
ctx.option = {
|
|
297
|
+
method: method || (body ? "POST" : "GET"),
|
|
298
|
+
headers: { ...(isJson && { "Content-Type": "application/json" }), ...headers },
|
|
299
|
+
...(body && { body: isJson ? JSON.stringify(body) : body }),
|
|
300
|
+
...offset
|
|
301
|
+
};
|
|
302
|
+
for (const func of before) {
|
|
303
|
+
if (typeof func !== 'function')
|
|
304
|
+
throw new VigorFetchError('Interceptor<before> is not a function', { type: "not a function", data: "before" });
|
|
305
|
+
const next = await func(ctx, ctx.option);
|
|
306
|
+
if (next !== undefined && typeof next === 'object' && !Array.isArray(next))
|
|
307
|
+
ctx = { ...ctx, ...next };
|
|
308
|
+
}
|
|
309
|
+
const originBase = ctx.origin.endsWith('/') ? ctx.origin : ctx.origin + '/';
|
|
310
|
+
const cleanPath = ctx.path.replace(/^\//, "");
|
|
311
|
+
const urlObj = cleanPath ? new URL(cleanPath, originBase) : new URL(ctx.origin);
|
|
312
|
+
Object.entries(query).forEach(([key, value]) => {
|
|
313
|
+
if (value !== null && value !== undefined)
|
|
314
|
+
urlObj.searchParams.append(key, String(value));
|
|
315
|
+
});
|
|
316
|
+
const url = urlObj.href;
|
|
317
|
+
ctx.url = url;
|
|
318
|
+
const fetchTarget = async () => {
|
|
319
|
+
const controller = new AbortController();
|
|
320
|
+
const abort = setTimeout(() => controller.abort(), limit);
|
|
321
|
+
const http = ctx.option;
|
|
322
|
+
http.signal = controller.signal;
|
|
323
|
+
const res = await fetch(url, http);
|
|
324
|
+
clearTimeout(abort);
|
|
325
|
+
return res;
|
|
326
|
+
};
|
|
327
|
+
const handle429 = async (ctx) => {
|
|
328
|
+
const res = ctx.result;
|
|
329
|
+
if (unretry.has(res.status))
|
|
330
|
+
throw new Error(`Unretry ${res.status}`);
|
|
331
|
+
if (!res || res.status !== 429)
|
|
332
|
+
return;
|
|
333
|
+
const rHeader = retryHeaders.map((h) => res.headers.get(h)).find(Boolean);
|
|
334
|
+
let delay = 0;
|
|
335
|
+
if (rHeader) {
|
|
336
|
+
delay = isNaN(Number(rHeader)) ? new Date(rHeader).getTime() - Date.now() : Number(rHeader) * 1000;
|
|
337
|
+
}
|
|
338
|
+
ctx.wait = Math.max(0, delay) + Math.random() * ctx.jitter;
|
|
339
|
+
if (ctx.wait > ctx.max)
|
|
340
|
+
throw new Error(`${url} Timeouted ${ctx.wait}ms`);
|
|
341
|
+
await new Promise(r => setTimeout(r, ctx.wait));
|
|
342
|
+
ctx.retry = true;
|
|
343
|
+
};
|
|
344
|
+
const retryInstance = new VigorRetry(fetchTarget, [], retryConfig).onRetry(handle429);
|
|
345
|
+
ctx.result = await retryInstance.request();
|
|
346
|
+
for (const func of after) {
|
|
347
|
+
if (typeof func !== 'function')
|
|
348
|
+
throw new VigorFetchError('Interceptor<after> is not a function', { type: "not a function", data: "after" });
|
|
349
|
+
const next = await func(ctx, ctx.result);
|
|
350
|
+
if (next !== undefined && typeof next === 'object' && !Array.isArray(next))
|
|
351
|
+
ctx = { ...ctx, ...next };
|
|
352
|
+
}
|
|
353
|
+
const parseInstance = new VigorParse(ctx.result, parseConfig);
|
|
354
|
+
ctx.final = await parseInstance.request();
|
|
355
|
+
for (const func of result) {
|
|
356
|
+
if (typeof func !== 'function')
|
|
357
|
+
throw new VigorFetchError('Interceptor<result> is not a function', { type: "not a function", data: "result" });
|
|
358
|
+
const next = await func(ctx.final);
|
|
359
|
+
if (next !== undefined && typeof next === 'object' && !Array.isArray(next))
|
|
360
|
+
ctx.final = { ...ctx, ...next };
|
|
361
|
+
}
|
|
362
|
+
if (ctx.final instanceof Error)
|
|
363
|
+
throw ctx.final;
|
|
364
|
+
return ctx.final;
|
|
365
|
+
}
|
|
366
|
+
catch (mainError) {
|
|
367
|
+
ctx.mainError = mainError;
|
|
368
|
+
for (const func of onError) {
|
|
369
|
+
if (typeof func !== 'function')
|
|
370
|
+
throw new VigorFetchError('Interceptor<onError> is not a function', { type: "not a function", data: "onError" });
|
|
371
|
+
const next = await func(ctx, ctx.mainError);
|
|
372
|
+
if (next !== undefined && typeof next === 'object' && !Array.isArray(next))
|
|
373
|
+
ctx = { ...ctx, ...next };
|
|
374
|
+
}
|
|
375
|
+
if (ctx.mainError instanceof Error)
|
|
376
|
+
throw ctx.mainError;
|
|
377
|
+
return ctx.mainError;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* VigorAll
|
|
383
|
+
*/
|
|
159
384
|
class VigorAll {
|
|
160
385
|
constructor(config) {
|
|
161
|
-
this._config =
|
|
386
|
+
this._config = {
|
|
387
|
+
settings: { limit: 10, jitter: 1000 },
|
|
388
|
+
request: { promises: [] },
|
|
389
|
+
response: { retryConfig: undefined, parseConfig: undefined },
|
|
390
|
+
interceptors: { before: [], after: [], onError: [] },
|
|
391
|
+
...config
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
_next(changes) {
|
|
395
|
+
return new this.constructor({
|
|
396
|
+
...this._config,
|
|
397
|
+
...changes,
|
|
398
|
+
settings: { ...this._config.settings, ...(changes.settings || {}) },
|
|
399
|
+
request: { ...this._config.request, ...(changes.request || {}) },
|
|
400
|
+
interceptors: { ...this._config.interceptors, ...(changes.interceptors || {}) }
|
|
401
|
+
});
|
|
162
402
|
}
|
|
163
|
-
|
|
164
|
-
limit(
|
|
165
|
-
jitter(
|
|
166
|
-
|
|
403
|
+
promises(...func) { return this._next({ request: { promises: [...this._config.request.promises, ...func.flat()] } }); }
|
|
404
|
+
limit(int) { return this._next({ settings: { limit: int } }); }
|
|
405
|
+
jitter(ms) { return this._next({ settings: { jitter: ms } }); }
|
|
406
|
+
before(...func) { return this._next({ interceptors: { before: [...this._config.interceptors.before, ...func.flat()] } }); }
|
|
407
|
+
after(...func) { return this._next({ interceptors: { after: [...this._config.interceptors.after, ...func.flat()] } }); }
|
|
408
|
+
onError(...func) { return this._next({ interceptors: { onError: [...this._config.interceptors.onError, ...func.flat()] } }); }
|
|
167
409
|
async request() {
|
|
168
|
-
const { limit, jitter, promises } = this._config;
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
410
|
+
const { settings: { limit, jitter }, request: { promises }, interceptors: { before, after, onError } } = this._config;
|
|
411
|
+
let ctx = { limit, jitter, promises, result: null };
|
|
412
|
+
try {
|
|
413
|
+
for (const func of before || []) {
|
|
414
|
+
if (typeof func !== 'function')
|
|
415
|
+
throw new VigorAllError('Interceptor<before> is not a function', { type: "not a function", data: "before" });
|
|
416
|
+
const next = await func(ctx, ctx.promises);
|
|
417
|
+
if (next !== undefined && typeof next === 'object' && !Array.isArray(next))
|
|
418
|
+
ctx = { ...ctx, ...next };
|
|
419
|
+
}
|
|
420
|
+
const results = [];
|
|
421
|
+
const executing = new Set();
|
|
422
|
+
for (const task of ctx.promises) {
|
|
423
|
+
const p = Promise.resolve()
|
|
424
|
+
.then(() => new Promise(res => setTimeout(res, Math.random() * ctx.jitter)))
|
|
425
|
+
.then(() => task());
|
|
426
|
+
results.push(p);
|
|
427
|
+
executing.add(p);
|
|
428
|
+
p.finally(() => executing.delete(p));
|
|
429
|
+
if (executing.size >= ctx.limit) {
|
|
430
|
+
await Promise.race(executing);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
const ready = await Promise.allSettled(results);
|
|
434
|
+
ctx.result = ready.map(i => {
|
|
435
|
+
if (i.status === "fulfilled")
|
|
436
|
+
return i.value;
|
|
437
|
+
return i.reason instanceof VigorAllError ? i.reason : new VigorAllError(i.reason?.message || "Unknown", { message: i.reason?.message || "Unknown" });
|
|
438
|
+
});
|
|
439
|
+
for (const func of after) {
|
|
440
|
+
if (typeof func !== 'function')
|
|
441
|
+
throw new VigorAllError('Interceptor<after> is not a function', { type: "not a function", data: "after" });
|
|
442
|
+
const next = await func(ctx, ctx.result);
|
|
443
|
+
if (next !== undefined && typeof next === 'object' && !Array.isArray(next))
|
|
444
|
+
ctx = { ...ctx, ...next };
|
|
445
|
+
}
|
|
446
|
+
if (ctx.result instanceof Error)
|
|
447
|
+
throw ctx.result;
|
|
448
|
+
return ctx.result;
|
|
449
|
+
}
|
|
450
|
+
catch (mainError) {
|
|
451
|
+
ctx.mainError = mainError;
|
|
452
|
+
for (const func of onError) {
|
|
453
|
+
if (typeof func !== 'function')
|
|
454
|
+
throw new VigorAllError('Interceptor<onError> is not a function', { type: "not a function", data: "onError" });
|
|
455
|
+
const next = await func(ctx, ctx.mainError);
|
|
456
|
+
if (next !== undefined && typeof next === 'object' && !Array.isArray(next))
|
|
457
|
+
ctx = { ...ctx, ...next };
|
|
458
|
+
}
|
|
459
|
+
if (ctx.mainError instanceof Error)
|
|
460
|
+
throw ctx.mainError;
|
|
461
|
+
return ctx.mainError;
|
|
180
462
|
}
|
|
181
|
-
const ready = await Promise.allSettled(results);
|
|
182
|
-
return ready.map(i => {
|
|
183
|
-
if (i.status === "fulfilled")
|
|
184
|
-
return i.value;
|
|
185
|
-
return i.reason instanceof VigorError ? i.reason : new VigorError(i.reason?.message || "Unknown", { message: i.reason?.message || "Unknown" });
|
|
186
|
-
});
|
|
187
463
|
}
|
|
188
464
|
}
|
|
465
|
+
/**
|
|
466
|
+
* Main Vigor Class
|
|
467
|
+
*/
|
|
189
468
|
class Vigor {
|
|
469
|
+
constructor() {
|
|
470
|
+
this._Fetch = VigorFetch;
|
|
471
|
+
this._Retry = VigorRetry;
|
|
472
|
+
this._Parse = VigorParse;
|
|
473
|
+
this._All = VigorAll;
|
|
474
|
+
}
|
|
475
|
+
use(plugin, options = {}) {
|
|
476
|
+
if (typeof plugin === 'function') {
|
|
477
|
+
plugin(this, options);
|
|
478
|
+
}
|
|
479
|
+
return this;
|
|
480
|
+
}
|
|
190
481
|
fetch(origin, config) {
|
|
191
|
-
return new
|
|
482
|
+
return new this._Fetch(origin, config);
|
|
483
|
+
}
|
|
484
|
+
retry(target, args, config) {
|
|
485
|
+
return new this._Retry(target, args, config);
|
|
486
|
+
}
|
|
487
|
+
parse(response, config) {
|
|
488
|
+
return new this._Parse(response, config);
|
|
192
489
|
}
|
|
193
490
|
all(config) {
|
|
194
|
-
return new
|
|
491
|
+
return new this._All(config);
|
|
195
492
|
}
|
|
196
493
|
}
|
|
197
494
|
const vigor = new Vigor();
|
|
495
|
+
const vigorInstance = vigor;
|
|
496
|
+
vigorInstance.VigorError = VigorError;
|
|
497
|
+
vigorInstance.VigorRetryError = VigorRetryError;
|
|
498
|
+
vigorInstance.VigorParseError = VigorParseError;
|
|
499
|
+
vigorInstance.VigorFetchError = VigorFetchError;
|
|
500
|
+
vigorInstance.VigorAllError = VigorAllError;
|
|
501
|
+
vigorInstance.VigorFetch = VigorFetch;
|
|
502
|
+
vigorInstance.VigorRetry = VigorRetry;
|
|
503
|
+
vigorInstance.VigorParse = VigorParse;
|
|
504
|
+
vigorInstance.VigorAll = VigorAll;
|
|
198
505
|
|
|
199
|
-
|
|
506
|
+
exports.VigorAll = VigorAll;
|
|
507
|
+
exports.VigorAllError = VigorAllError;
|
|
508
|
+
exports.VigorError = VigorError;
|
|
509
|
+
exports.VigorFetch = VigorFetch;
|
|
510
|
+
exports.VigorFetchError = VigorFetchError;
|
|
511
|
+
exports.VigorParse = VigorParse;
|
|
512
|
+
exports.VigorParseError = VigorParseError;
|
|
513
|
+
exports.VigorRetry = VigorRetry;
|
|
514
|
+
exports.VigorRetryError = VigorRetryError;
|
|
515
|
+
exports.default = vigor;
|
|
516
|
+
exports.vigor = vigor;
|