vigor-fetch 2.0.1 → 2.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 +1 -1
- package/dist/index.d.ts +363 -101
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +707 -448
- package/dist/index.mjs +700 -448
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,534 +1,786 @@
|
|
|
1
1
|
class VigorError extends Error {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
this.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
2
|
+
timestamp;
|
|
3
|
+
method;
|
|
4
|
+
cause;
|
|
5
|
+
context;
|
|
6
|
+
type;
|
|
7
|
+
data;
|
|
8
|
+
constructor(message, options) {
|
|
9
|
+
super(message, { cause: options?.cause });
|
|
10
|
+
this.name = new.target.name;
|
|
11
|
+
this.timestamp = new Date();
|
|
12
|
+
if (options?.method !== undefined)
|
|
13
|
+
this.method = options.method;
|
|
14
|
+
if (options?.context !== undefined)
|
|
15
|
+
this.context = options.context;
|
|
16
|
+
if (options?.type !== undefined)
|
|
17
|
+
this.type = options.type;
|
|
18
|
+
if (options?.data !== undefined)
|
|
19
|
+
this.data = options.data;
|
|
20
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
21
|
+
Error.captureStackTrace?.(this, new.target);
|
|
14
22
|
}
|
|
15
23
|
}
|
|
16
24
|
class VigorRetryError extends VigorError {
|
|
17
|
-
constructor(
|
|
18
|
-
super(
|
|
19
|
-
this.message = options.message || `[VigorRetryError] ${text}`;
|
|
25
|
+
constructor(message, options) {
|
|
26
|
+
super(message, options);
|
|
20
27
|
}
|
|
21
28
|
}
|
|
22
29
|
class VigorParseError extends VigorError {
|
|
23
|
-
constructor(
|
|
24
|
-
super(
|
|
25
|
-
this.message = options.message || `[VigorParseError] ${text}`;
|
|
30
|
+
constructor(message, options) {
|
|
31
|
+
super(message, options);
|
|
26
32
|
}
|
|
27
33
|
}
|
|
28
34
|
class VigorFetchError extends VigorError {
|
|
29
|
-
constructor(
|
|
30
|
-
super(
|
|
31
|
-
this.message = options.message || `[VigorFetchError] ${text}`;
|
|
35
|
+
constructor(message, options) {
|
|
36
|
+
super(message, options);
|
|
32
37
|
}
|
|
33
38
|
}
|
|
34
39
|
class VigorAllError extends VigorError {
|
|
35
|
-
constructor(
|
|
36
|
-
super(
|
|
37
|
-
this.message = options.message || `[VigorAllError] ${text}`;
|
|
40
|
+
constructor(message, options) {
|
|
41
|
+
super(message, options);
|
|
38
42
|
}
|
|
39
43
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
constructor(
|
|
45
|
-
this.
|
|
46
|
-
this.
|
|
47
|
-
this.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
44
|
+
class VigorStatus {
|
|
45
|
+
_config;
|
|
46
|
+
_ctor;
|
|
47
|
+
_errorCtor;
|
|
48
|
+
constructor(_config, _ctor, _errorCtor) {
|
|
49
|
+
this._config = _config;
|
|
50
|
+
this._ctor = _ctor;
|
|
51
|
+
this._errorCtor = _errorCtor;
|
|
52
|
+
}
|
|
53
|
+
_create(config) { return this._ctor(config); }
|
|
54
|
+
_next(config) { return this._create({ ...this._config, ...config }); }
|
|
55
|
+
getConfig() { return this._config; }
|
|
56
|
+
_pipeSub(value, Ctor, fn, errorKey) {
|
|
57
|
+
const ErrorCtor = this._errorCtor?.();
|
|
58
|
+
if (typeof fn !== "function" && ErrorCtor) {
|
|
59
|
+
throw new ErrorCtor("ctor expects function", {
|
|
60
|
+
method: errorKey,
|
|
61
|
+
type: "invalid_input",
|
|
62
|
+
data: { expected: "function", received: fn }
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return fn(new Ctor(value)).getConfig();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
class VigorRetrySettings extends VigorStatus {
|
|
69
|
+
_base;
|
|
70
|
+
constructor(config) {
|
|
71
|
+
const base = {
|
|
72
|
+
count: 5,
|
|
73
|
+
limit: 10000,
|
|
74
|
+
maxDelay: 10000,
|
|
55
75
|
};
|
|
76
|
+
super({ ...base, ...config }, (c) => new VigorRetrySettings(c));
|
|
77
|
+
this._base = base;
|
|
56
78
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
79
|
+
getBase() { return this._base; }
|
|
80
|
+
count(num) { return this._next({ count: num }); }
|
|
81
|
+
limit(num) { return this._next({ limit: num }); }
|
|
82
|
+
maxDelay(num) { return this._next({ maxDelay: num }); }
|
|
83
|
+
default(obj) { return this._next({ default: obj }); }
|
|
84
|
+
}
|
|
85
|
+
class VigorRetryBackoff extends VigorStatus {
|
|
86
|
+
_base;
|
|
87
|
+
constructor(config) {
|
|
88
|
+
const base = {
|
|
89
|
+
initialDelay: 0,
|
|
90
|
+
baseDelay: 1000,
|
|
91
|
+
factor: 1.7,
|
|
92
|
+
jitter: 1000
|
|
93
|
+
};
|
|
94
|
+
super({ ...base, ...config }, (c) => new VigorRetryBackoff(c));
|
|
95
|
+
this._base = base;
|
|
96
|
+
}
|
|
97
|
+
getBase() { return this._base; }
|
|
98
|
+
initialDelay(num) { return this._next({ initialDelay: num }); }
|
|
99
|
+
baseDelay(num) { return this._next({ baseDelay: num }); }
|
|
100
|
+
factor(num) { return this._next({ factor: num }); }
|
|
101
|
+
jitter(num) { return this._next({ jitter: num }); }
|
|
102
|
+
}
|
|
103
|
+
class VigorRetryInterceptors extends VigorStatus {
|
|
104
|
+
_base;
|
|
105
|
+
constructor(config) {
|
|
106
|
+
const base = {
|
|
107
|
+
before: [],
|
|
108
|
+
after: [],
|
|
109
|
+
onError: [],
|
|
110
|
+
onRetry: [],
|
|
111
|
+
retryIf: []
|
|
112
|
+
};
|
|
113
|
+
super({ ...base, ...config }, (c) => new VigorRetryInterceptors(c));
|
|
114
|
+
this._base = base;
|
|
115
|
+
}
|
|
116
|
+
getBase() { return this._base; }
|
|
117
|
+
before(...funcs) { return this._next({ before: [...this.getConfig().before, ...funcs.flat()] }); }
|
|
118
|
+
after(...funcs) { return this._next({ after: [...this.getConfig().after, ...funcs.flat()] }); }
|
|
119
|
+
onError(...funcs) { return this._next({ onError: [...this.getConfig().onError, ...funcs.flat()] }); }
|
|
120
|
+
onRetry(...funcs) { return this._next({ onRetry: [...this.getConfig().onRetry, ...funcs.flat()] }); }
|
|
121
|
+
retryIf(...funcs) { return this._next({ retryIf: [...this.getConfig().retryIf, ...funcs.flat()] }); }
|
|
122
|
+
}
|
|
123
|
+
class VigorRetry extends VigorStatus {
|
|
124
|
+
_base;
|
|
125
|
+
_controller = new AbortController();
|
|
126
|
+
constructor(config) {
|
|
127
|
+
const base = {
|
|
128
|
+
target: null,
|
|
129
|
+
setting: new VigorRetrySettings().getBase(),
|
|
130
|
+
backoff: new VigorRetryBackoff().getBase(),
|
|
131
|
+
interceptors: new VigorRetryInterceptors().getBase()
|
|
132
|
+
};
|
|
133
|
+
super({ ...base, ...config }, (c) => new VigorRetry(c), () => VigorRetryError);
|
|
134
|
+
this._base = base;
|
|
135
|
+
}
|
|
136
|
+
getBase() { return this._base; }
|
|
137
|
+
target(func) { return new VigorRetry({ ...this._config, target: func, setting: this._config.setting, interceptors: this._config.interceptors }); }
|
|
138
|
+
createController() { const controller = new AbortController(); this._controller = controller; return (error) => controller.abort(error); }
|
|
139
|
+
setting(func) {
|
|
140
|
+
return this._next({
|
|
141
|
+
setting: this._pipeSub(this._config.setting, VigorRetrySettings, func, "setting")
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
backoff(func) {
|
|
145
|
+
return this._next({
|
|
146
|
+
backoff: this._pipeSub(this._config.backoff, VigorRetryBackoff, func, "backoff")
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
interceptors(func) {
|
|
150
|
+
return this._next({
|
|
151
|
+
interceptors: this._pipeSub(this._config.interceptors, VigorRetryInterceptors, func, "interceptors")
|
|
69
152
|
});
|
|
70
153
|
}
|
|
71
|
-
args(...args) { return new this.constructor(this._target, args, this._config); }
|
|
72
|
-
count(int) { return this._next({ retry: { count: int } }); }
|
|
73
|
-
max(ms) { return this._next({ retry: { max: ms } }); }
|
|
74
|
-
backoff(ms) { return this._next({ retry: { backoff: ms } }); }
|
|
75
|
-
baseDelay(ms) { return this._next({ retry: { baseDelay: ms } }); }
|
|
76
|
-
jitter(ms) { return this._next({ retry: { jitter: ms } }); }
|
|
77
|
-
before(...func) { return this._next({ interceptors: { before: [...this._config.interceptors.before, ...func.flat()] } }); }
|
|
78
|
-
onRetry(...func) { return this._next({ interceptors: { onRetry: [...this._config.interceptors.onRetry, ...func.flat()] } }); }
|
|
79
|
-
after(...func) { return this._next({ interceptors: { after: [...this._config.interceptors.after, ...func.flat()] } }); }
|
|
80
|
-
onError(...func) { return this._next({ interceptors: { onError: [...this._config.interceptors.onError, ...func.flat()] } }); }
|
|
81
154
|
async request() {
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
155
|
+
const config = this._config;
|
|
156
|
+
let ctx = {
|
|
157
|
+
target: config.target,
|
|
158
|
+
setting: { ...config.setting },
|
|
159
|
+
interceptors: {
|
|
160
|
+
before: [...config.interceptors.before],
|
|
161
|
+
after: [...config.interceptors.after],
|
|
162
|
+
onError: [...config.interceptors.onError],
|
|
163
|
+
onRetry: [...config.interceptors.onRetry],
|
|
164
|
+
retryIf: [...config.interceptors.retryIf],
|
|
165
|
+
},
|
|
166
|
+
backoff: { ...config.backoff },
|
|
167
|
+
runtime: {
|
|
168
|
+
result: null,
|
|
169
|
+
controller: null,
|
|
170
|
+
attempt: 0,
|
|
171
|
+
aborted: false,
|
|
172
|
+
signal: null,
|
|
173
|
+
delay: 0,
|
|
174
|
+
retry: false,
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
const throwError = (error) => { throw error; };
|
|
178
|
+
const normalizeError = (obj) => {
|
|
179
|
+
if (obj instanceof Error) {
|
|
180
|
+
throw obj;
|
|
181
|
+
}
|
|
182
|
+
throw new Error(String(obj));
|
|
183
|
+
};
|
|
85
184
|
try {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
ctx.
|
|
92
|
-
ctx.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (typeof func !== 'function')
|
|
96
|
-
throw new VigorRetryError('Interceptor<before> is not a function', { type: "not a function", data: "before" });
|
|
97
|
-
const next = await func(ctx, ctx.args);
|
|
98
|
-
if (next !== undefined && typeof next === 'object' && !Array.isArray(next))
|
|
99
|
-
ctx = { ...ctx, ...next };
|
|
100
|
-
}
|
|
101
|
-
if (!ctx.try)
|
|
102
|
-
break;
|
|
185
|
+
while (ctx.runtime.attempt < ctx.setting.count) {
|
|
186
|
+
ctx.runtime.attempt++;
|
|
187
|
+
ctx.runtime.controller = new AbortController();
|
|
188
|
+
let listener;
|
|
189
|
+
let timerId;
|
|
190
|
+
const setAttempt = (attempt) => ctx.runtime.attempt = attempt;
|
|
191
|
+
const abort = (error) => { if (!ctx.runtime.aborted) {
|
|
192
|
+
ctx.runtime.controller?.abort(error);
|
|
193
|
+
} };
|
|
103
194
|
try {
|
|
104
|
-
ctx.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (
|
|
110
|
-
ctx
|
|
195
|
+
ctx.runtime.signal = AbortSignal.any([
|
|
196
|
+
this._controller.signal,
|
|
197
|
+
ctx.runtime.controller.signal
|
|
198
|
+
]);
|
|
199
|
+
ctx.runtime.abortPromise = new Promise((_, reject) => {
|
|
200
|
+
if (ctx.runtime.signal.aborted)
|
|
201
|
+
reject(ctx.runtime.signal.reason);
|
|
202
|
+
listener = () => {
|
|
203
|
+
ctx.runtime.aborted = true;
|
|
204
|
+
reject(ctx.runtime.signal.reason);
|
|
205
|
+
};
|
|
206
|
+
ctx.runtime.signal.addEventListener("abort", listener, { once: true });
|
|
207
|
+
timerId = setTimeout(() => {
|
|
208
|
+
if (ctx.runtime.aborted)
|
|
209
|
+
return;
|
|
210
|
+
abort(new VigorRetryError(`timeouted after ${ctx.setting.limit}`, { method: "request", type: "timeout", data: { limit: ctx.setting.limit, attempt: ctx.runtime.attempt } }));
|
|
211
|
+
}, ctx.setting.limit);
|
|
212
|
+
});
|
|
213
|
+
for (const func of ctx.interceptors.before) {
|
|
214
|
+
await func(ctx, { setAttempt, throwError, abort });
|
|
215
|
+
if (ctx.runtime.signal.aborted)
|
|
216
|
+
normalizeError(ctx.runtime.signal.reason);
|
|
217
|
+
}
|
|
218
|
+
ctx.runtime.result = await Promise.race([
|
|
219
|
+
ctx.target(ctx, { abort, signal: ctx.runtime.signal }),
|
|
220
|
+
ctx.runtime.abortPromise
|
|
221
|
+
]);
|
|
222
|
+
const setResult = (result) => ctx.runtime.result = result;
|
|
223
|
+
for (const func of ctx.interceptors.after) {
|
|
224
|
+
await func(ctx, { setAttempt, setResult, throwError });
|
|
225
|
+
if (ctx.runtime.signal.aborted)
|
|
226
|
+
normalizeError(ctx.runtime.signal.reason);
|
|
111
227
|
}
|
|
112
|
-
|
|
113
|
-
throw ctx.error;
|
|
114
|
-
if (ctx.result instanceof Error)
|
|
115
|
-
throw ctx.result;
|
|
116
|
-
return ctx.result;
|
|
228
|
+
return ctx.runtime.result;
|
|
117
229
|
}
|
|
118
230
|
catch (error) {
|
|
119
|
-
ctx.
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
231
|
+
if (ctx.runtime.aborted)
|
|
232
|
+
normalizeError(ctx.runtime.signal.reason);
|
|
233
|
+
ctx.runtime.retry = true;
|
|
234
|
+
ctx.runtime.error = error;
|
|
235
|
+
const proceedRetry = () => ctx.runtime.retry = true;
|
|
236
|
+
const cancelRetry = (error) => { ctx.runtime.error = error; return (ctx.runtime.retry = false); };
|
|
237
|
+
for (const func of ctx.interceptors.retryIf) {
|
|
238
|
+
await func(ctx, { throwError, proceedRetry, cancelRetry });
|
|
239
|
+
}
|
|
240
|
+
if (!ctx.runtime.retry) {
|
|
241
|
+
throw ctx.runtime.error;
|
|
127
242
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
243
|
+
ctx.runtime.delay = Math.min(ctx.setting.maxDelay, Math.max(0, ctx.backoff.initialDelay + ctx.backoff.baseDelay * Math.pow(ctx.backoff.factor, ctx.runtime.attempt - 1))) + calculateJitter(ctx.backoff.jitter);
|
|
244
|
+
const setDelay = (delay) => ctx.runtime.delay = delay;
|
|
245
|
+
for (const func of ctx.interceptors.onRetry) {
|
|
246
|
+
await func(ctx, { setAttempt, throwError, setDelay });
|
|
247
|
+
}
|
|
248
|
+
await new Promise((resolve, reject) => {
|
|
249
|
+
const timer = setTimeout(resolve, ctx.runtime.delay);
|
|
250
|
+
const abortHandler = () => {
|
|
251
|
+
clearTimeout(timer);
|
|
252
|
+
reject(this._controller.signal.reason);
|
|
253
|
+
};
|
|
254
|
+
if (this._controller.signal.aborted)
|
|
255
|
+
return abortHandler();
|
|
256
|
+
this._controller.signal.addEventListener("abort", abortHandler, { once: true });
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
finally {
|
|
260
|
+
clearTimeout(timerId);
|
|
261
|
+
if (listener)
|
|
262
|
+
ctx.runtime.signal.removeEventListener("abort", listener);
|
|
131
263
|
}
|
|
132
264
|
}
|
|
133
|
-
|
|
134
|
-
throw ctx.error;
|
|
135
|
-
if (ctx.result instanceof Error)
|
|
136
|
-
throw ctx.result;
|
|
265
|
+
throw new VigorRetryError(`Maximum retry attempts (${ctx.setting.count}) reached. Task failed or timed out.`, { method: "request", type: "exhausted", data: { limit: ctx.setting.limit, attempt: ctx.runtime.attempt, maxAttempts: ctx.setting.count } });
|
|
137
266
|
}
|
|
138
|
-
catch (
|
|
139
|
-
ctx.
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (next !== undefined && typeof next === 'object' && !Array.isArray(next))
|
|
145
|
-
ctx = { ...ctx, ...next };
|
|
267
|
+
catch (error) {
|
|
268
|
+
ctx.runtime.error = error;
|
|
269
|
+
let overrided = false;
|
|
270
|
+
const setResult = (result) => { overrided = true; return (ctx.runtime.result = result); };
|
|
271
|
+
for (const func of ctx.interceptors.onError) {
|
|
272
|
+
await func(ctx, { setResult, throwError });
|
|
146
273
|
}
|
|
147
|
-
if (ctx.
|
|
148
|
-
|
|
149
|
-
|
|
274
|
+
if (overrided && ctx.runtime.result !== undefined)
|
|
275
|
+
return ctx.runtime.result;
|
|
276
|
+
if (ctx.setting.default !== undefined)
|
|
277
|
+
return ctx.setting.default;
|
|
278
|
+
throw error;
|
|
150
279
|
}
|
|
151
|
-
return ctx.result;
|
|
152
280
|
}
|
|
153
281
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
282
|
+
const basic = { key: /text/, parse: (res) => res.text(), type: "text" };
|
|
283
|
+
const parser = [
|
|
284
|
+
{ key: /json/, parse: (res) => res.json(), type: "json" },
|
|
285
|
+
{ key: /multipart\/form-data/, parse: (res) => res.formData(), type: "formData" },
|
|
286
|
+
{ key: /octet-stream/, parse: (res) => res.arrayBuffer(), type: "arrayBuffer" },
|
|
287
|
+
{ key: /(image|video|audio|pdf)/, parse: (res) => res.blob(), type: "blob" },
|
|
288
|
+
basic
|
|
289
|
+
];
|
|
290
|
+
const supported = parser.map(i => i.type);
|
|
291
|
+
class VigorParse extends VigorStatus {
|
|
292
|
+
_base;
|
|
293
|
+
constructor(config) {
|
|
294
|
+
const base = {
|
|
295
|
+
original: false
|
|
164
296
|
};
|
|
297
|
+
super({ ...base, ...config }, (c) => new VigorParse(c));
|
|
298
|
+
this._base = base;
|
|
165
299
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
settings: {
|
|
171
|
-
...this._config.settings,
|
|
172
|
-
...(changes.settings || {})
|
|
173
|
-
},
|
|
174
|
-
interceptors: {
|
|
175
|
-
...this._config.interceptors,
|
|
176
|
-
...(changes.interceptors || {})
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
original(bool) { return this._next({ settings: { original: bool } }); }
|
|
181
|
-
type(str) { return this._next({ settings: { parse: str } }); }
|
|
182
|
-
before(...func) { return this._next({ interceptors: { before: [...this._config.interceptors.before, ...func.flat()] } }); }
|
|
183
|
-
after(...func) { return this._next({ interceptors: { after: [...this._config.interceptors.after, ...func.flat()] } }); }
|
|
184
|
-
onError(...func) { return this._next({ interceptors: { onError: [...this._config.interceptors.onError, ...func.flat()] } }); }
|
|
300
|
+
getBase() { return this._base; }
|
|
301
|
+
target(response) { return this._next({ target: response }); }
|
|
302
|
+
original(bool) { return this._next({ original: bool }); }
|
|
303
|
+
type(str) { return this._next({ type: str }); }
|
|
185
304
|
async request() {
|
|
186
|
-
const
|
|
187
|
-
|
|
305
|
+
const config = this._config;
|
|
306
|
+
if (!config.target)
|
|
307
|
+
throw new VigorParseError("target is required", { method: "request", type: "invalid_target", data: {
|
|
308
|
+
expected: "Response",
|
|
309
|
+
received: config.target,
|
|
310
|
+
} });
|
|
311
|
+
if (config.original)
|
|
312
|
+
return config.target;
|
|
313
|
+
const contentType = config.target.headers.get("Content-Type") || "";
|
|
314
|
+
let strategy;
|
|
188
315
|
try {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const method = response[ctx.parse];
|
|
201
|
-
if (!method || typeof method !== 'function')
|
|
202
|
-
throw new VigorParseError(`Invalid method such as ${ctx.parse}`, { type: "Invalid method", data: ctx.parse });
|
|
203
|
-
return await method.call(response);
|
|
204
|
-
}
|
|
205
|
-
const contentType = response.headers.get("Content-Type") || "";
|
|
206
|
-
if (/json/.test(contentType))
|
|
207
|
-
return await response.json();
|
|
208
|
-
if (/multipart\/form-data/.test(contentType))
|
|
209
|
-
return await response.formData();
|
|
210
|
-
if (/octet-stream/.test(contentType))
|
|
211
|
-
return await response.arrayBuffer();
|
|
212
|
-
if (/(image|video|audio|pdf)/.test(contentType))
|
|
213
|
-
return await response.blob();
|
|
214
|
-
return await response.text();
|
|
215
|
-
})(ctx.response);
|
|
216
|
-
for (const func of after) {
|
|
217
|
-
if (typeof func !== 'function')
|
|
218
|
-
throw new VigorParseError('Interceptor<after> is not a function', { type: "not a function", data: "after" });
|
|
219
|
-
const next = await func(ctx, ctx.result);
|
|
220
|
-
if (next !== undefined && typeof next === 'object' && !Array.isArray(next))
|
|
221
|
-
ctx = { ...ctx, ...next };
|
|
316
|
+
if (config.type) {
|
|
317
|
+
strategy = { type: config.type };
|
|
318
|
+
const parser = config.target[config.type];
|
|
319
|
+
if (!parser || typeof parser !== 'function')
|
|
320
|
+
throw new VigorParseError(`failed to parse: '${strategy?.type ?? "unknown"}'`, { method: "request", type: "invalid_type", data: {
|
|
321
|
+
expected: config.type,
|
|
322
|
+
supported: supported,
|
|
323
|
+
response: config.target,
|
|
324
|
+
headers: contentType,
|
|
325
|
+
} });
|
|
326
|
+
return await parser();
|
|
222
327
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
return ctx.result;
|
|
328
|
+
strategy = parser.find(i => i.key.test(contentType)) ?? basic;
|
|
329
|
+
return await strategy.parse(config.target);
|
|
226
330
|
}
|
|
227
|
-
catch (
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
throw ctx.mainError;
|
|
238
|
-
return ctx.mainError;
|
|
331
|
+
catch (error) {
|
|
332
|
+
if (error instanceof VigorParseError)
|
|
333
|
+
throw error;
|
|
334
|
+
throw new VigorParseError(`failed to parse: '${strategy?.type ?? "unknown"}'`, { method: "request", type: "parse_failed", data: {
|
|
335
|
+
expected: strategy?.type ?? "unknown",
|
|
336
|
+
supported: supported,
|
|
337
|
+
response: config.target,
|
|
338
|
+
headers: contentType,
|
|
339
|
+
error
|
|
340
|
+
} });
|
|
239
341
|
}
|
|
240
342
|
}
|
|
241
343
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
interceptors: { before: [], after: [], onError: [], result: [] },
|
|
252
|
-
...config
|
|
344
|
+
class VigorFetchSettings extends VigorStatus {
|
|
345
|
+
_base;
|
|
346
|
+
constructor(config) {
|
|
347
|
+
const base = {
|
|
348
|
+
origin: "",
|
|
349
|
+
path: [],
|
|
350
|
+
query: {},
|
|
351
|
+
unretry: [400, 401, 403, 404, 405, 413, 422],
|
|
352
|
+
retryHeaders: ["retry-after", "ratelimit-reset", "x-ratelimit-reset", "x-retry-after", "x-amz-retry-after", "chrome-proxy-next-link"],
|
|
253
353
|
};
|
|
354
|
+
super({ ...base, ...config }, (c) => new VigorFetchSettings(c));
|
|
355
|
+
this._base = base;
|
|
254
356
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
357
|
+
getBase() { return this._base; }
|
|
358
|
+
origin(str) { return this._next({ origin: str }); }
|
|
359
|
+
path(...strs) { return this._next({ path: [...this._config.path, ...strs.flat()] }); }
|
|
360
|
+
query(obj) { return this._next({ query: { ...this._config.query, ...obj } }); }
|
|
361
|
+
unretry(...numbers) { return this._next({ unretry: numbers.flat() }); }
|
|
362
|
+
retryHeaders(...strs) { return this._next({ retryHeaders: [...this._config.retryHeaders, ...strs.flat()] }); }
|
|
363
|
+
method(str) { return this._next({ method: str }); }
|
|
364
|
+
headers(obj) { return this._next({ headers: obj }); }
|
|
365
|
+
body(obj) { return this._next({ body: obj }); }
|
|
366
|
+
options(obj) { return this._next({ options: obj }); }
|
|
367
|
+
default(obj) { return this._next({ default: obj }); }
|
|
368
|
+
}
|
|
369
|
+
class VigorFetchInterceptors extends VigorStatus {
|
|
370
|
+
_base;
|
|
371
|
+
constructor(config) {
|
|
372
|
+
const base = {
|
|
373
|
+
before: [],
|
|
374
|
+
after: [],
|
|
375
|
+
onError: [],
|
|
376
|
+
result: []
|
|
268
377
|
};
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
378
|
+
super({ ...base, ...config }, (c) => new VigorFetchInterceptors(c));
|
|
379
|
+
this._base = base;
|
|
380
|
+
}
|
|
381
|
+
getBase() { return this._base; }
|
|
382
|
+
before(...funcs) { return this._next({ before: [...this.getConfig().before, ...funcs.flat()] }); }
|
|
383
|
+
after(...funcs) { return this._next({ after: [...this.getConfig().after, ...funcs.flat()] }); }
|
|
384
|
+
onError(...funcs) { return this._next({ onError: [...this.getConfig().onError, ...funcs.flat()] }); }
|
|
385
|
+
result(...funcs) { return this._next({ result: [...this.getConfig().result, ...funcs.flat()] }); }
|
|
386
|
+
}
|
|
387
|
+
class VigorFetch extends VigorStatus {
|
|
388
|
+
_base;
|
|
389
|
+
constructor(config) {
|
|
390
|
+
const base = {
|
|
391
|
+
setting: new VigorFetchSettings().getBase(),
|
|
392
|
+
retryConfig: new VigorRetry().getBase(),
|
|
393
|
+
parseConfig: new VigorParse().getBase(),
|
|
394
|
+
interceptors: new VigorFetchInterceptors().getBase(),
|
|
395
|
+
};
|
|
396
|
+
super({ ...base, ...config }, (c) => new VigorFetch(c), () => VigorRetryError);
|
|
397
|
+
this._base = base;
|
|
398
|
+
}
|
|
399
|
+
getBase() { return this._base; }
|
|
400
|
+
origin(str) { return this._next({ setting: { ...this._config.setting, origin: str } }); }
|
|
401
|
+
path(...strs) { return this._next({ setting: { ...this._config.setting, path: [...this._config.setting.path, ...strs.flat()] } }); }
|
|
402
|
+
query(obj) { return this._next({ setting: { ...this._config.setting, query: { ...this._config.setting.query, ...obj } } }); }
|
|
403
|
+
method(str) { return this._next({ setting: { ...this._config.setting, method: str } }); }
|
|
404
|
+
headers(obj) { return this._next({ setting: { ...this._config.setting, headers: obj } }); }
|
|
405
|
+
body(obj) { return this._next({ setting: { ...this._config.setting, body: obj } }); }
|
|
406
|
+
options(obj) { return this._next({ setting: { ...this._config.setting, options: obj } }); }
|
|
407
|
+
setting(func) {
|
|
408
|
+
return this._next({
|
|
409
|
+
setting: this._pipeSub(this._config.setting, VigorFetchSettings, func, "setting")
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
retryConfig(func) {
|
|
413
|
+
return this._next({
|
|
414
|
+
retryConfig: this._pipeSub(this._config.retryConfig, VigorRetry, func, "retryConfig")
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
parseConfig(func) {
|
|
418
|
+
return this._next({
|
|
419
|
+
parseConfig: this._pipeSub(this._config.parseConfig, VigorParse, func, "parseConfig")
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
buildUrl(origin, path, query) {
|
|
423
|
+
if (!origin)
|
|
424
|
+
throw new VigorFetchError("buildUrl expects 'origin'", {
|
|
425
|
+
type: "invalid_input", method: "buildUrl", data: {
|
|
426
|
+
expected: "string", received: origin
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
try {
|
|
430
|
+
const url = new URL(origin);
|
|
431
|
+
if (path && path.length > 0) {
|
|
432
|
+
const cleanPath = path
|
|
433
|
+
.filter(p => p && typeof p === 'string')
|
|
434
|
+
.map(p => p.replace(/^\/+|\/+$/g, ''))
|
|
435
|
+
.join('/');
|
|
436
|
+
if (cleanPath) {
|
|
437
|
+
const base = url.pathname.endsWith('/') ? url.pathname : url.pathname + '/';
|
|
438
|
+
url.pathname = base + cleanPath;
|
|
276
439
|
}
|
|
440
|
+
}
|
|
441
|
+
if (query && typeof query === 'object') {
|
|
442
|
+
Object.entries(query).forEach(([key, value]) => {
|
|
443
|
+
if (value === null || value === undefined)
|
|
444
|
+
return;
|
|
445
|
+
if (Array.isArray(value)) {
|
|
446
|
+
value.forEach(v => url.searchParams.append(key, String(v)));
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
url.searchParams.set(key, String(value));
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
return url.toString();
|
|
454
|
+
}
|
|
455
|
+
catch (e) {
|
|
456
|
+
throw new VigorFetchError(`Invalid URL origin: ${origin}`, {
|
|
457
|
+
type: "invalid_url", method: "buildUrl", data: { error: e }
|
|
277
458
|
});
|
|
278
459
|
}
|
|
279
|
-
return new this.constructor(newConfig.request.origin, newConfig);
|
|
280
|
-
}
|
|
281
|
-
origin(str) { return this._next({ request: { origin: str } }); }
|
|
282
|
-
path(str) { return this._next({ request: { path: str } }); }
|
|
283
|
-
query(obj) { return this._next({ request: { query: obj } }); }
|
|
284
|
-
method(str) { return this._next({ request: { method: str } }); }
|
|
285
|
-
headers(obj) { return this._next({ request: { headers: obj } }); }
|
|
286
|
-
body(obj) { return this._next({ request: { body: obj } }); }
|
|
287
|
-
offset(obj) { return this._next({ request: { offset: obj } }); }
|
|
288
|
-
limit(ms) { return this._next({ retry: { limit: ms } }); }
|
|
289
|
-
retryHeaders(...str) { return this._next({ retry: { retryHeaders: [...this._config.retry.retryHeaders, ...str.flat()] } }); }
|
|
290
|
-
unretry(...int) { return this._next({ retry: { unretry: new Set(int.flat()) } }); }
|
|
291
|
-
before(...func) { return this._next({ interceptors: { before: [...this._config.interceptors.before, ...func.flat()] } }); }
|
|
292
|
-
after(...func) { return this._next({ interceptors: { after: [...this._config.interceptors.after, ...func.flat()] } }); }
|
|
293
|
-
result(...func) { return this._next({ interceptors: { result: [...this._config.interceptors.result, ...func.flat()] } }); }
|
|
294
|
-
onError(...func) { return this._next({ interceptors: { onError: [...this._config.interceptors.onError, ...func.flat()] } }); }
|
|
295
|
-
retryConfig(func) {
|
|
296
|
-
if (typeof func !== 'function')
|
|
297
|
-
throw new VigorFetchError("retryConfig is not a function", { type: "not a function", data: "retryConfig" });
|
|
298
|
-
const dummyRetry = func(new VigorRetry(async () => { }));
|
|
299
|
-
return this._next({ retry: { retryConfig: dummyRetry['_config'] } });
|
|
300
460
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
return this._next({ response: { parseConfig: dummyParse['_config'] } });
|
|
461
|
+
interceptors(func) {
|
|
462
|
+
return this._next({
|
|
463
|
+
interceptors: this._pipeSub(this._config.interceptors, VigorFetchInterceptors, func, "interceptors")
|
|
464
|
+
});
|
|
306
465
|
}
|
|
307
466
|
async request() {
|
|
308
|
-
const
|
|
309
|
-
let ctx = {
|
|
467
|
+
const config = this._config;
|
|
468
|
+
let ctx = {
|
|
469
|
+
setting: { ...config.setting },
|
|
470
|
+
retryConfig: {
|
|
471
|
+
...config.retryConfig,
|
|
472
|
+
interceptors: {
|
|
473
|
+
before: [...config.retryConfig.interceptors.before],
|
|
474
|
+
after: [...config.retryConfig.interceptors.after],
|
|
475
|
+
onError: [...config.retryConfig.interceptors.onError],
|
|
476
|
+
onRetry: [...config.retryConfig.interceptors.onRetry],
|
|
477
|
+
retryIf: [...config.retryConfig.interceptors.retryIf],
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
parseConfig: {
|
|
481
|
+
...config.parseConfig
|
|
482
|
+
},
|
|
483
|
+
interceptors: {
|
|
484
|
+
before: [...config.interceptors.before],
|
|
485
|
+
after: [...config.interceptors.after],
|
|
486
|
+
onError: [...config.interceptors.onError],
|
|
487
|
+
result: [...config.interceptors.result]
|
|
488
|
+
},
|
|
489
|
+
runtime: {}
|
|
490
|
+
};
|
|
491
|
+
const throwError = (error) => { throw error; };
|
|
310
492
|
try {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
493
|
+
ctx.runtime.unretrySet = new Set(ctx.setting.unretry);
|
|
494
|
+
if (!/^(https?|data|blob|file|about):\/\//.test(ctx.setting.origin))
|
|
495
|
+
throw new VigorFetchError(`Invalid Protocol`, { type: "Invalid Protocol", method: "request", data: {
|
|
496
|
+
expected: ["http", "https", "data", "blob", "file", "about"], received: ctx.setting.origin
|
|
497
|
+
} });
|
|
498
|
+
ctx.runtime.url = this.buildUrl(config.setting.origin, config.setting.path, config.setting.query);
|
|
499
|
+
const isJson = Array.isArray(ctx.setting.body) || (!!ctx.setting.body && Object.getPrototypeOf(ctx.setting.body) === Object.prototype);
|
|
500
|
+
ctx.runtime.baseOptions = {
|
|
501
|
+
method: ctx.setting.method || (ctx.setting.body ? "POST" : "GET"),
|
|
502
|
+
headers: { ...(isJson && { "Content-Type": "application/json" }), ...ctx.setting.headers },
|
|
503
|
+
...(ctx.setting.body && { body: isJson ? JSON.stringify(ctx.setting.body) : ctx.setting.body }),
|
|
504
|
+
...ctx.setting.options,
|
|
505
|
+
signal: null
|
|
319
506
|
};
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
const originBase = ctx.origin.endsWith('/') ? ctx.origin : ctx.origin + '/';
|
|
328
|
-
const cleanPath = ctx.path.replace(/^\//, "");
|
|
329
|
-
const urlObj = cleanPath ? new URL(cleanPath, originBase) : new URL(ctx.origin);
|
|
330
|
-
Object.entries(query).forEach(([key, value]) => {
|
|
331
|
-
if (value !== null && value !== undefined)
|
|
332
|
-
urlObj.searchParams.append(key, String(value));
|
|
333
|
-
});
|
|
334
|
-
const url = urlObj.href;
|
|
335
|
-
ctx.url = url;
|
|
336
|
-
const fetchTarget = async () => {
|
|
337
|
-
const controller = new AbortController();
|
|
338
|
-
const abort = setTimeout(() => controller.abort(), limit);
|
|
339
|
-
const http = ctx.option;
|
|
340
|
-
http.signal = controller.signal;
|
|
341
|
-
const res = await fetch(url, http);
|
|
342
|
-
clearTimeout(abort);
|
|
343
|
-
return res;
|
|
507
|
+
const target = async (ctx2, { signal }) => {
|
|
508
|
+
ctx.runtime.options = {
|
|
509
|
+
...ctx.runtime.baseOptions,
|
|
510
|
+
signal
|
|
511
|
+
};
|
|
512
|
+
const response = await fetch(ctx.runtime.url, ctx.runtime.options);
|
|
513
|
+
return response;
|
|
344
514
|
};
|
|
345
|
-
const
|
|
346
|
-
const
|
|
347
|
-
if (
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
515
|
+
const checkOk = async (ctx2, { throwError }) => {
|
|
516
|
+
const result = ctx2.runtime.result;
|
|
517
|
+
if (!result.ok)
|
|
518
|
+
return throwError?.(new VigorFetchError(`HTTP Error: ${result.status} ${result.statusText}`, {
|
|
519
|
+
method: "request", type: "fetch_error",
|
|
520
|
+
data: { status: result.status, statusText: result.statusText, url: result.url }
|
|
521
|
+
}));
|
|
522
|
+
};
|
|
523
|
+
const handleBlacklist = (ctx2, { cancelRetry }) => {
|
|
524
|
+
const result = ctx2.runtime.result;
|
|
525
|
+
if (!result?.status || ctx.runtime.unretrySet.has(result.status))
|
|
526
|
+
cancelRetry?.();
|
|
527
|
+
};
|
|
528
|
+
const handle429 = (ctx2, { setDelay }) => {
|
|
529
|
+
const result = ctx2.runtime.result;
|
|
530
|
+
if (result?.status === 429) {
|
|
531
|
+
let rHeader = null;
|
|
532
|
+
ctx.setting.retryHeaders.some(h => (rHeader = result.headers.get(h)));
|
|
533
|
+
if (rHeader) {
|
|
534
|
+
setDelay?.(isNaN(Number(rHeader)) ? new Date(rHeader).getTime() - Date.now() : Number(rHeader) * 1000);
|
|
535
|
+
}
|
|
355
536
|
}
|
|
356
|
-
ctx.wait = Math.max(0, delay) + Math.random() * ctx.jitter;
|
|
357
|
-
if (ctx.wait > ctx.max)
|
|
358
|
-
throw new Error(`${url} Timeouted ${ctx.wait}ms`);
|
|
359
|
-
await new Promise(r => setTimeout(r, ctx.wait));
|
|
360
|
-
ctx.retry = true;
|
|
361
537
|
};
|
|
362
|
-
|
|
363
|
-
ctx.
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
538
|
+
ctx.retryConfig.target = target;
|
|
539
|
+
ctx.retryConfig.interceptors.after.unshift(checkOk);
|
|
540
|
+
ctx.retryConfig.interceptors.retryIf.unshift(handleBlacklist);
|
|
541
|
+
ctx.retryConfig.interceptors.onRetry.unshift(handle429);
|
|
542
|
+
ctx.runtime.retryEngine = new VigorRetry(ctx.retryConfig);
|
|
543
|
+
ctx.runtime.parseEngine = new VigorParse(ctx.parseConfig);
|
|
544
|
+
const setOptions = (obj) => ctx.runtime.baseOptions = obj;
|
|
545
|
+
for (const func of ctx.interceptors.before) {
|
|
546
|
+
await func(ctx, { setOptions, throwError });
|
|
370
547
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
for (const func of finalInterceptors) {
|
|
375
|
-
if (typeof func !== 'function')
|
|
376
|
-
continue;
|
|
377
|
-
const next = await func(ctx.final);
|
|
378
|
-
if (next !== undefined)
|
|
379
|
-
ctx.final = next;
|
|
548
|
+
ctx.runtime.response = await ctx.runtime.retryEngine.request();
|
|
549
|
+
for (const func of ctx.interceptors.after) {
|
|
550
|
+
await func(ctx, { throwError });
|
|
380
551
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
552
|
+
ctx.runtime.result = await ctx.runtime.parseEngine?.target(ctx.runtime.response).request();
|
|
553
|
+
const setResult = (result) => ctx.runtime.result = result;
|
|
554
|
+
for (const func of ctx.interceptors.result) {
|
|
555
|
+
await func(ctx, { setResult, throwError });
|
|
556
|
+
}
|
|
557
|
+
return ctx.runtime.result;
|
|
384
558
|
}
|
|
385
|
-
catch (
|
|
386
|
-
ctx.
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
if (next !== undefined && typeof next === 'object' && !Array.isArray(next))
|
|
392
|
-
ctx = { ...ctx, ...next };
|
|
559
|
+
catch (error) {
|
|
560
|
+
ctx.runtime.error = error;
|
|
561
|
+
let overrided = false;
|
|
562
|
+
const setResult = (result) => { overrided = true; return (ctx.runtime.result = result); };
|
|
563
|
+
for (const func of ctx.interceptors.onError) {
|
|
564
|
+
await func(ctx, { setResult, throwError });
|
|
393
565
|
}
|
|
394
|
-
if (ctx.
|
|
395
|
-
|
|
396
|
-
|
|
566
|
+
if (overrided && ctx.runtime.result !== undefined)
|
|
567
|
+
return ctx.runtime.result;
|
|
568
|
+
if (ctx.setting.default !== undefined)
|
|
569
|
+
return ctx.setting.default;
|
|
570
|
+
throw error;
|
|
397
571
|
}
|
|
398
572
|
}
|
|
399
573
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
*/
|
|
403
|
-
class VigorAll {
|
|
574
|
+
class VigorAllSettings extends VigorStatus {
|
|
575
|
+
_base;
|
|
404
576
|
constructor(config) {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
response: { retryConfig: undefined, parseConfig: undefined },
|
|
409
|
-
interceptors: { before: [], after: [], onError: [] },
|
|
410
|
-
...config
|
|
577
|
+
const base = {
|
|
578
|
+
concurrency: 5,
|
|
579
|
+
jitter: 1000
|
|
411
580
|
};
|
|
581
|
+
super({ ...base, ...config }, (c) => new VigorAllSettings(c));
|
|
582
|
+
this._base = base;
|
|
412
583
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
584
|
+
getBase() { return this._base; }
|
|
585
|
+
concurrency(num) { return this._next({ concurrency: num }); }
|
|
586
|
+
jitter(num) { return this._next({ jitter: num }); }
|
|
587
|
+
}
|
|
588
|
+
class VigorAllInterceptors extends VigorStatus {
|
|
589
|
+
_base;
|
|
590
|
+
constructor(config) {
|
|
591
|
+
const base = {
|
|
592
|
+
before: [],
|
|
593
|
+
after: [],
|
|
594
|
+
onError: [],
|
|
595
|
+
result: []
|
|
596
|
+
};
|
|
597
|
+
super({ ...base, ...config }, (c) => new VigorAllInterceptors(c));
|
|
598
|
+
this._base = base;
|
|
599
|
+
}
|
|
600
|
+
getBase() { return this._base; }
|
|
601
|
+
before(...funcs) { return this._next({ before: [...this.getConfig().before, ...funcs.flat()] }); }
|
|
602
|
+
after(...funcs) { return this._next({ after: [...this.getConfig().after, ...funcs.flat()] }); }
|
|
603
|
+
onError(...funcs) { return this._next({ onError: [...this.getConfig().onError, ...funcs.flat()] }); }
|
|
604
|
+
result(...funcs) { return this._next({ result: [...this.getConfig().result, ...funcs.flat()] }); }
|
|
605
|
+
}
|
|
606
|
+
class VigorAll extends VigorStatus {
|
|
607
|
+
_base;
|
|
608
|
+
constructor(config) {
|
|
609
|
+
const base = {
|
|
610
|
+
target: [],
|
|
611
|
+
setting: new VigorAllSettings().getBase(),
|
|
612
|
+
interceptors: new VigorAllInterceptors().getBase()
|
|
613
|
+
};
|
|
614
|
+
super({ ...base, ...config }, (c) => new VigorAll(c), () => VigorAllError);
|
|
615
|
+
this._base = base;
|
|
616
|
+
}
|
|
617
|
+
getBase() { return this._base; }
|
|
618
|
+
target(...funcs) { return this._next({ target: [...this._config.target, ...funcs.flat()] }); }
|
|
619
|
+
setting(func) {
|
|
620
|
+
return this._next({
|
|
621
|
+
setting: this._pipeSub(this._config.setting, VigorAllSettings, func, "setting")
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
interceptors(func) {
|
|
625
|
+
return this._next({
|
|
626
|
+
interceptors: this._pipeSub(this._config.interceptors, VigorAllInterceptors, func, "interceptors")
|
|
429
627
|
});
|
|
430
628
|
}
|
|
431
|
-
promises(...func) { return this._next({ request: { promises: [...this._config.request.promises, ...func.flat()] } }); }
|
|
432
|
-
limit(int) { return this._next({ settings: { limit: int } }); }
|
|
433
|
-
jitter(ms) { return this._next({ settings: { jitter: ms } }); }
|
|
434
|
-
before(...func) { return this._next({ interceptors: { before: [...this._config.interceptors.before, ...func.flat()] } }); }
|
|
435
|
-
after(...func) { return this._next({ interceptors: { after: [...this._config.interceptors.after, ...func.flat()] } }); }
|
|
436
|
-
onError(...func) { return this._next({ interceptors: { onError: [...this._config.interceptors.onError, ...func.flat()] } }); }
|
|
437
629
|
async request() {
|
|
438
|
-
const
|
|
439
|
-
let ctx = {
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
630
|
+
const config = this._config;
|
|
631
|
+
let ctx = {
|
|
632
|
+
target: [...config.target],
|
|
633
|
+
setting: { ...config.setting },
|
|
634
|
+
interceptors: {
|
|
635
|
+
before: [...config.interceptors.before],
|
|
636
|
+
after: [...config.interceptors.after],
|
|
637
|
+
onError: [...config.interceptors.onError],
|
|
638
|
+
result: [...config.interceptors.result]
|
|
639
|
+
},
|
|
640
|
+
runtime: {
|
|
641
|
+
tasks: [],
|
|
642
|
+
result: []
|
|
447
643
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
644
|
+
};
|
|
645
|
+
if (ctx.target?.length == 0)
|
|
646
|
+
throw new VigorFetchError("request expects 'target'", {
|
|
647
|
+
type: "invalid_input", method: "request", data: {
|
|
648
|
+
expected: "string", received: ctx.target
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
let active = 0;
|
|
652
|
+
const queue = [];
|
|
653
|
+
const runTask = async (task) => {
|
|
654
|
+
await new Promise(resolve => {
|
|
655
|
+
if (active < ctx.setting.concurrency) {
|
|
656
|
+
active++;
|
|
657
|
+
resolve();
|
|
658
|
+
}
|
|
659
|
+
else {
|
|
660
|
+
queue.push(() => {
|
|
661
|
+
active++;
|
|
662
|
+
resolve();
|
|
663
|
+
});
|
|
459
664
|
}
|
|
460
|
-
}
|
|
461
|
-
const ready = await Promise.allSettled(results);
|
|
462
|
-
ctx.result = ready.map(i => {
|
|
463
|
-
if (i.status === "fulfilled")
|
|
464
|
-
return i.value;
|
|
465
|
-
return i.reason instanceof VigorAllError ? i.reason : new VigorAllError(i.reason?.message || "Unknown", { message: i.reason?.message || "Unknown" });
|
|
466
665
|
});
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
ctx
|
|
666
|
+
const throwError = (error) => { throw error; };
|
|
667
|
+
try {
|
|
668
|
+
await new Promise(resolve => setTimeout(resolve, ctx.setting.jitter));
|
|
669
|
+
let res;
|
|
670
|
+
for (const func of ctx.interceptors.before) {
|
|
671
|
+
await func(ctx, { throwError });
|
|
672
|
+
}
|
|
673
|
+
res = await task(ctx, {});
|
|
674
|
+
const setResult = (result) => res = result;
|
|
675
|
+
for (const func of ctx.interceptors.after) {
|
|
676
|
+
await func(ctx, { setResult, throwError });
|
|
677
|
+
}
|
|
678
|
+
return res;
|
|
473
679
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
if (
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
680
|
+
catch (error) {
|
|
681
|
+
let res;
|
|
682
|
+
let overrided = false;
|
|
683
|
+
const setResult = (result) => { overrided = true; return (res = result); };
|
|
684
|
+
for (const func of ctx.interceptors.onError) {
|
|
685
|
+
await func(ctx, { setResult, throwError });
|
|
686
|
+
}
|
|
687
|
+
if (overrided && res !== undefined)
|
|
688
|
+
return res;
|
|
689
|
+
throw error;
|
|
690
|
+
}
|
|
691
|
+
finally {
|
|
692
|
+
active--;
|
|
693
|
+
const next = queue.shift();
|
|
694
|
+
if (next)
|
|
695
|
+
next();
|
|
486
696
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
697
|
+
};
|
|
698
|
+
ctx.runtime.tasks = ctx.target.map(task => runTask(task));
|
|
699
|
+
const settled = await Promise.allSettled(ctx.runtime.tasks);
|
|
700
|
+
ctx.runtime.result = settled.map(i => {
|
|
701
|
+
if (i.status === "fulfilled")
|
|
702
|
+
return i.value;
|
|
703
|
+
return new VigorAllError(`this request failed`, {
|
|
704
|
+
method: "request", type: "request_failed", data: {
|
|
705
|
+
error: i.reason
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
});
|
|
709
|
+
const setResult = (result) => ctx.runtime.result = result;
|
|
710
|
+
const throwError = (error) => { throw error; };
|
|
711
|
+
for (const func of ctx.interceptors.result) {
|
|
712
|
+
await func(ctx, { setResult, throwError });
|
|
490
713
|
}
|
|
714
|
+
return ctx.runtime.result;
|
|
491
715
|
}
|
|
492
716
|
}
|
|
493
|
-
|
|
494
|
-
*
|
|
495
|
-
|
|
717
|
+
function calculateJitter(jitter) {
|
|
718
|
+
return jitter * (Math.random() * 2 - 1);
|
|
719
|
+
}
|
|
496
720
|
class Vigor {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
721
|
+
registry;
|
|
722
|
+
constructor(config) {
|
|
723
|
+
const defaultRegistry = {
|
|
724
|
+
VigorRetry: {
|
|
725
|
+
main: () => new VigorRetry(),
|
|
726
|
+
error: VigorRetryError,
|
|
727
|
+
setting: VigorRetrySettings,
|
|
728
|
+
interceptors: VigorRetryInterceptors,
|
|
729
|
+
backoff: VigorRetryBackoff,
|
|
730
|
+
},
|
|
731
|
+
VigorFetch: {
|
|
732
|
+
main: () => new VigorFetch(),
|
|
733
|
+
error: VigorFetchError,
|
|
734
|
+
setting: VigorFetchSettings,
|
|
735
|
+
interceptors: VigorFetchInterceptors,
|
|
736
|
+
},
|
|
737
|
+
VigorAll: {
|
|
738
|
+
main: () => new VigorAll(),
|
|
739
|
+
error: VigorAllError,
|
|
740
|
+
setting: VigorAllSettings,
|
|
741
|
+
interceptors: VigorAllInterceptors,
|
|
742
|
+
},
|
|
743
|
+
VigorParse: {
|
|
744
|
+
main: () => new VigorParse(),
|
|
745
|
+
error: VigorParseError,
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
this.registry = config?.registry ?? defaultRegistry;
|
|
749
|
+
}
|
|
750
|
+
fetch(origin) {
|
|
751
|
+
return this.registry.VigorFetch.main().origin(origin);
|
|
508
752
|
}
|
|
509
|
-
|
|
510
|
-
return
|
|
753
|
+
all(tasks) {
|
|
754
|
+
return this.registry.VigorAll.main().target(tasks.flat());
|
|
511
755
|
}
|
|
512
|
-
|
|
513
|
-
return
|
|
756
|
+
parse(response) {
|
|
757
|
+
return this.registry.VigorParse.main().target(response);
|
|
514
758
|
}
|
|
515
|
-
|
|
516
|
-
return
|
|
759
|
+
retry(fn) {
|
|
760
|
+
return this.registry.VigorRetry.main().target(fn);
|
|
517
761
|
}
|
|
518
|
-
|
|
519
|
-
|
|
762
|
+
use(plugin, options) {
|
|
763
|
+
const nextRegistry = {
|
|
764
|
+
...this.registry,
|
|
765
|
+
VigorFetch: {
|
|
766
|
+
...this.registry.VigorFetch
|
|
767
|
+
},
|
|
768
|
+
VigorRetry: {
|
|
769
|
+
...this.registry.VigorRetry
|
|
770
|
+
},
|
|
771
|
+
VigorAll: {
|
|
772
|
+
...this.registry.VigorAll
|
|
773
|
+
},
|
|
774
|
+
VigorParse: {
|
|
775
|
+
...this.registry.VigorParse
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
plugin(nextRegistry, options);
|
|
779
|
+
return new Vigor({
|
|
780
|
+
registry: nextRegistry
|
|
781
|
+
});
|
|
520
782
|
}
|
|
521
783
|
}
|
|
522
784
|
const vigor = new Vigor();
|
|
523
|
-
const vigorInstance = vigor;
|
|
524
|
-
vigorInstance.VigorError = VigorError;
|
|
525
|
-
vigorInstance.VigorRetryError = VigorRetryError;
|
|
526
|
-
vigorInstance.VigorParseError = VigorParseError;
|
|
527
|
-
vigorInstance.VigorFetchError = VigorFetchError;
|
|
528
|
-
vigorInstance.VigorAllError = VigorAllError;
|
|
529
|
-
vigorInstance.VigorFetch = VigorFetch;
|
|
530
|
-
vigorInstance.VigorRetry = VigorRetry;
|
|
531
|
-
vigorInstance.VigorParse = VigorParse;
|
|
532
|
-
vigorInstance.VigorAll = VigorAll;
|
|
533
785
|
|
|
534
|
-
export { VigorAll, VigorAllError,
|
|
786
|
+
export { Vigor, VigorAll, VigorAllError, VigorAllInterceptors, VigorAllSettings, VigorFetch, VigorFetchError, VigorFetchInterceptors, VigorFetchSettings, VigorParse, VigorParseError, VigorRetry, VigorRetryBackoff, VigorRetryError, VigorRetryInterceptors, VigorRetrySettings, vigor as default, vigor };
|