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