vigor-fetch 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,158 +2,144 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
+ const VIGOR_ERROR_MESSAGES = {
6
+ TIMEOUT: ({ limit, attempt }) => `Timeout: exceeded ${limit}ms (attempt: ${attempt})`,
7
+ EXHAUSTED: ({ maxAttempts }) => `Retry exhausted: max ${maxAttempts})`,
8
+ INVALID_URL: ({ received }) => `Invalid URL: ${received}`,
9
+ INVALID_PROTOCOL: ({ expected, received }) => `Invalid protocol: ${received} (expected ${expected.join(", ")})`,
10
+ FETCH_ERROR: ({ status, statusText, url }) => `HTTP Error: ${status} ${statusText} (url: ${url})`,
11
+ PARSE_FAILED: ({ expected }) => `Parse failed: expected ${expected}`,
12
+ INVALID_TYPE: ({ expected, received }) => `Invalid parser type: ${expected}`,
13
+ TARGET_MISSING: () => `Target missing`,
14
+ REQUEST_FAILED: ({ index, error }) => `Request failed at index ${index}: ${error.message}`,
15
+ UNKNOWN: () => `Unknown error`
16
+ };
5
17
  class VigorError extends Error {
6
- timestamp;
18
+ timestamp = new Date();
7
19
  method;
20
+ code;
8
21
  cause;
9
22
  context;
10
23
  type;
11
24
  data;
12
- constructor(message, options) {
25
+ constructor(code, options) {
26
+ const messageFn = VIGOR_ERROR_MESSAGES[code];
27
+ const message = `[${code}] ${messageFn(options?.data)}`;
13
28
  super(message, { cause: options?.cause });
14
29
  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;
30
+ this.method = options?.method;
31
+ this.code = code;
32
+ this.context = options?.context;
33
+ this.type = options?.type;
34
+ this.data = options.data;
24
35
  Object.setPrototypeOf(this, new.target.prototype);
25
36
  Error.captureStackTrace?.(this, new.target);
26
37
  }
27
38
  }
28
39
  class VigorRetryError extends VigorError {
29
- constructor(message, options) {
30
- super(message, options);
40
+ constructor(code, options) {
41
+ super(code, options);
31
42
  }
32
43
  }
33
44
  class VigorParseError extends VigorError {
34
- constructor(message, options) {
35
- super(message, options);
45
+ constructor(code, options) {
46
+ super(code, options);
36
47
  }
37
48
  }
38
49
  class VigorFetchError extends VigorError {
39
- constructor(message, options) {
40
- super(message, options);
50
+ constructor(code, options) {
51
+ super(code, options);
41
52
  }
42
53
  }
43
54
  class VigorAllError extends VigorError {
44
- constructor(message, options) {
45
- super(message, options);
55
+ constructor(code, options) {
56
+ super(code, options);
46
57
  }
47
58
  }
59
+ const EMPTY = Symbol("EMPTY");
48
60
  class VigorStatus {
61
+ _base;
49
62
  _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 }); }
63
+ constructor(config, _base) {
64
+ this._base = _base;
65
+ this._config = { ...this._base, ...config };
66
+ }
59
67
  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();
68
+ getBase() { return this._base; }
69
+ _next(config) { return new this.constructor({ ...this._config, ...config }, this._base); }
70
+ _pipsub(config, fn, ctor) {
71
+ return fn(new ctor(config)).getConfig();
70
72
  }
71
73
  }
72
74
  class VigorRetrySettings extends VigorStatus {
73
- _base;
74
- constructor(config) {
75
- const base = {
75
+ constructor(config = {}) {
76
+ super(config, {
76
77
  count: 5,
77
78
  limit: 10000,
78
79
  maxDelay: 10000,
79
- };
80
- super({ ...base, ...config }, (c) => new VigorRetrySettings(c));
81
- this._base = base;
80
+ default: EMPTY
81
+ });
82
82
  }
83
- getBase() { return this._base; }
84
83
  count(num) { return this._next({ count: num }); }
85
84
  limit(num) { return this._next({ limit: num }); }
86
85
  maxDelay(num) { return this._next({ maxDelay: num }); }
87
86
  default(obj) { return this._next({ default: obj }); }
88
87
  }
89
88
  class VigorRetryBackoff extends VigorStatus {
90
- _base;
91
- constructor(config) {
92
- const base = {
89
+ constructor(config = {}) {
90
+ super(config, {
93
91
  initialDelay: 0,
94
92
  baseDelay: 1000,
95
93
  factor: 1.7,
96
94
  jitter: 1000
97
- };
98
- super({ ...base, ...config }, (c) => new VigorRetryBackoff(c));
99
- this._base = base;
95
+ });
100
96
  }
101
- getBase() { return this._base; }
102
97
  initialDelay(num) { return this._next({ initialDelay: num }); }
103
98
  baseDelay(num) { return this._next({ baseDelay: num }); }
104
99
  factor(num) { return this._next({ factor: num }); }
105
100
  jitter(num) { return this._next({ jitter: num }); }
101
+ static randomJitter(num) { return Math.random() * num; }
106
102
  }
107
103
  class VigorRetryInterceptors extends VigorStatus {
108
- _base;
109
- constructor(config) {
110
- const base = {
104
+ constructor(config = {}) {
105
+ super(config, {
111
106
  before: [],
112
107
  after: [],
113
108
  onError: [],
114
109
  onRetry: [],
115
110
  retryIf: []
116
- };
117
- super({ ...base, ...config }, (c) => new VigorRetryInterceptors(c));
118
- this._base = base;
111
+ });
119
112
  }
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()] }); }
113
+ before(...funcs) { return this._next({ ...this._config, before: [...this._config.before, ...funcs.flat()] }); }
114
+ after(...funcs) { return this._next({ ...this._config, after: [...this._config.after, ...funcs.flat()] }); }
115
+ onError(...funcs) { return this._next({ ...this._config, onError: [...this._config.onError, ...funcs.flat()] }); }
116
+ onRetry(...funcs) { return this._next({ ...this._config, onRetry: [...this._config.onRetry, ...funcs.flat()] }); }
117
+ retryIf(...funcs) { return this._next({ ...this._config, retryIf: [...this._config.retryIf, ...funcs.flat()] }); }
126
118
  }
127
119
  class VigorRetry extends VigorStatus {
128
- _base;
129
- _controller = new AbortController();
130
- constructor(config) {
131
- const base = {
120
+ constructor(config = {}) {
121
+ super(config, {
132
122
  target: null,
133
123
  setting: new VigorRetrySettings().getBase(),
134
124
  backoff: new VigorRetryBackoff().getBase(),
135
- interceptors: new VigorRetryInterceptors().getBase()
136
- };
137
- super({ ...base, ...config }, (c) => new VigorRetry(c), () => VigorRetryError);
138
- this._base = base;
125
+ interceptors: new VigorRetryInterceptors().getBase(),
126
+ controller: config.controller || new AbortController()
127
+ });
139
128
  }
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); }
129
+ _transfer(config) { return new VigorRetry({ ...this._config, ...config }); }
130
+ _calculateDelay(initialDelay, baseDelay, factor, attempt, jitter, maxDelay) {
131
+ return Math.max(0, Math.min(maxDelay, initialDelay + baseDelay * Math.pow(factor, attempt) + VigorRetryBackoff.randomJitter(jitter)));
132
+ }
133
+ createController() { return this._config.controller = new AbortController(); }
134
+ target(func) { return this._transfer({ target: func }); }
143
135
  setting(func) {
144
- return this._next({
145
- setting: this._pipeSub(this._config.setting, VigorRetrySettings, func, "setting")
146
- });
136
+ return this._next({ setting: this._pipsub(this._config.setting, func, VigorRetrySettings) });
147
137
  }
148
138
  backoff(func) {
149
- return this._next({
150
- backoff: this._pipeSub(this._config.backoff, VigorRetryBackoff, func, "backoff")
151
- });
139
+ return this._next({ backoff: this._pipsub(this._config.backoff, func, VigorRetryBackoff) });
152
140
  }
153
141
  interceptors(func) {
154
- return this._next({
155
- interceptors: this._pipeSub(this._config.interceptors, VigorRetryInterceptors, func, "interceptors")
156
- });
142
+ return this._next({ interceptors: this._pipsub(this._config.interceptors, func, VigorRetryInterceptors) });
157
143
  }
158
144
  async request() {
159
145
  const config = this._config;
@@ -168,8 +154,9 @@ class VigorRetry extends VigorStatus {
168
154
  retryIf: [...config.interceptors.retryIf],
169
155
  },
170
156
  backoff: { ...config.backoff },
157
+ controller: config.controller,
171
158
  runtime: {
172
- result: null,
159
+ result: EMPTY,
173
160
  controller: null,
174
161
  attempt: 0,
175
162
  aborted: false,
@@ -187,7 +174,6 @@ class VigorRetry extends VigorStatus {
187
174
  };
188
175
  try {
189
176
  while (ctx.runtime.attempt < ctx.setting.count) {
190
- ctx.runtime.attempt++;
191
177
  ctx.runtime.controller = new AbortController();
192
178
  let listener;
193
179
  let timerId;
@@ -197,7 +183,7 @@ class VigorRetry extends VigorStatus {
197
183
  } };
198
184
  try {
199
185
  ctx.runtime.signal = AbortSignal.any([
200
- this._controller.signal,
186
+ ctx.controller.signal,
201
187
  ctx.runtime.controller.signal
202
188
  ]);
203
189
  ctx.runtime.abortPromise = new Promise((_, reject) => {
@@ -211,7 +197,14 @@ class VigorRetry extends VigorStatus {
211
197
  timerId = setTimeout(() => {
212
198
  if (ctx.runtime.aborted)
213
199
  return;
214
- abort(new VigorRetryError(`timeouted after ${ctx.setting.limit}`, { method: "request", type: "timeout", data: { limit: ctx.setting.limit, attempt: ctx.runtime.attempt } }));
200
+ abort(new VigorRetryError("TIMEOUT", {
201
+ method: "request",
202
+ type: "timeout",
203
+ data: {
204
+ limit: ctx.setting.limit,
205
+ attempt: ctx.runtime.attempt
206
+ }
207
+ }));
215
208
  }, ctx.setting.limit);
216
209
  });
217
210
  for (const func of ctx.interceptors.before) {
@@ -244,7 +237,8 @@ class VigorRetry extends VigorStatus {
244
237
  if (!ctx.runtime.retry) {
245
238
  throw ctx.runtime.error;
246
239
  }
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);
240
+ const { initialDelay, baseDelay, factor, jitter } = ctx.backoff;
241
+ ctx.runtime.delay = this._calculateDelay(initialDelay, baseDelay, factor, ctx.runtime.attempt, jitter, ctx.setting.maxDelay);
248
242
  const setDelay = (delay) => ctx.runtime.delay = delay;
249
243
  for (const func of ctx.interceptors.onRetry) {
250
244
  await func(ctx, { setAttempt, throwError, setDelay });
@@ -253,11 +247,11 @@ class VigorRetry extends VigorStatus {
253
247
  const timer = setTimeout(resolve, ctx.runtime.delay);
254
248
  const abortHandler = () => {
255
249
  clearTimeout(timer);
256
- reject(this._controller.signal.reason);
250
+ reject(ctx.controller.signal.reason);
257
251
  };
258
- if (this._controller.signal.aborted)
252
+ if (ctx.controller.signal.aborted)
259
253
  return abortHandler();
260
- this._controller.signal.addEventListener("abort", abortHandler, { once: true });
254
+ ctx.controller.signal.addEventListener("abort", abortHandler, { once: true });
261
255
  });
262
256
  }
263
257
  finally {
@@ -265,8 +259,15 @@ class VigorRetry extends VigorStatus {
265
259
  if (listener)
266
260
  ctx.runtime.signal.removeEventListener("abort", listener);
267
261
  }
262
+ ctx.runtime.attempt++;
268
263
  }
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 } });
264
+ throw new VigorRetryError("EXHAUSTED", {
265
+ method: "request",
266
+ type: "retry",
267
+ data: {
268
+ maxAttempts: ctx.setting.count,
269
+ }
270
+ });
270
271
  }
271
272
  catch (error) {
272
273
  ctx.runtime.error = error;
@@ -275,45 +276,44 @@ class VigorRetry extends VigorStatus {
275
276
  for (const func of ctx.interceptors.onError) {
276
277
  await func(ctx, { setResult, throwError });
277
278
  }
278
- if (overrided && ctx.runtime.result !== undefined)
279
+ if (overrided && ctx.runtime.result !== EMPTY)
279
280
  return ctx.runtime.result;
280
- if (ctx.setting.default !== undefined)
281
+ if (ctx.setting.default !== EMPTY)
281
282
  return ctx.setting.default;
282
283
  throw error;
283
284
  }
284
285
  }
285
286
  }
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
287
  class VigorParse extends VigorStatus {
296
- _base;
297
- constructor(config) {
298
- const base = {
288
+ constructor(config = {}) {
289
+ super(config, {
299
290
  original: false
300
- };
301
- super({ ...base, ...config }, (c) => new VigorParse(c));
302
- this._base = base;
291
+ });
303
292
  }
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 }); }
293
+ static stategy = [
294
+ { key: /text/, parse: (res) => res.text(), type: "text" },
295
+ { key: /json/, parse: (res) => res.json(), type: "json" },
296
+ { key: /multipart\/form-data/, parse: (res) => res.formData(), type: "formData" },
297
+ { key: /octet-stream/, parse: (res) => res.arrayBuffer(), type: "arrayBuffer" },
298
+ { key: /(image|video|audio|pdf)/, parse: (res) => res.blob(), type: "blob" },
299
+ ];
300
+ static supported = this.stategy.map(i => i.type);
301
+ _transfer(config) { return new VigorParse({ ...this._config, ...config }); }
302
+ target(res) { return this._next({ target: res }); }
303
+ original(bool) { return this._transfer({ ...this._config, original: bool }); }
304
+ type(type) { return this._transfer({ ...this._config, result: undefined, type }); }
308
305
  async request() {
309
306
  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)
307
+ if (!(config.target instanceof Response)) {
308
+ throw new VigorParseError("TARGET_MISSING", {
309
+ method: "request",
310
+ type: "args_missing",
311
+ data: undefined
312
+ });
313
+ }
314
+ if (config.original) {
316
315
  return config.target;
316
+ }
317
317
  const contentType = config.target.headers.get("Content-Type") || "";
318
318
  let strategy;
319
319
  try {
@@ -321,44 +321,42 @@ class VigorParse extends VigorStatus {
321
321
  strategy = { type: config.type };
322
322
  const parser = config.target[config.type];
323
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
- } });
324
+ throw new VigorParseError("PARSE_FAILED", {
325
+ method: "request",
326
+ type: "parse_failed",
327
+ data: {
328
+ expected: strategy?.type ?? "unknown"
329
+ }
330
+ });
330
331
  return await parser();
331
332
  }
332
- strategy = parser.find(i => i.key.test(contentType)) ?? basic;
333
+ strategy = VigorParse.stategy.find(i => i.key.test(contentType)) ?? VigorParse.stategy[0];
333
334
  return await strategy.parse(config.target);
334
335
  }
335
336
  catch (error) {
336
337
  if (error instanceof VigorParseError)
337
338
  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
- } });
339
+ throw new VigorParseError("PARSE_FAILED", {
340
+ method: "request",
341
+ type: "parse_failed",
342
+ data: {
343
+ expected: strategy?.type ?? "unknown"
344
+ }
345
+ });
345
346
  }
346
347
  }
347
348
  }
348
349
  class VigorFetchSettings extends VigorStatus {
349
- _base;
350
- constructor(config) {
351
- const base = {
350
+ constructor(config = {}) {
351
+ super(config, {
352
352
  origin: "",
353
353
  path: [],
354
354
  query: {},
355
355
  unretry: [400, 401, 403, 404, 405, 413, 422],
356
356
  retryHeaders: ["retry-after", "ratelimit-reset", "x-ratelimit-reset", "x-retry-after", "x-amz-retry-after", "chrome-proxy-next-link"],
357
- };
358
- super({ ...base, ...config }, (c) => new VigorFetchSettings(c));
359
- this._base = base;
357
+ default: EMPTY
358
+ });
360
359
  }
361
- getBase() { return this._base; }
362
360
  origin(str) { return this._next({ origin: str }); }
363
361
  path(...strs) { return this._next({ path: [...this._config.path, ...strs.flat()] }); }
364
362
  query(obj) { return this._next({ query: { ...this._config.query, ...obj } }); }
@@ -371,36 +369,28 @@ class VigorFetchSettings extends VigorStatus {
371
369
  default(obj) { return this._next({ default: obj }); }
372
370
  }
373
371
  class VigorFetchInterceptors extends VigorStatus {
374
- _base;
375
- constructor(config) {
376
- const base = {
372
+ constructor(config = {}) {
373
+ super(config, {
377
374
  before: [],
378
375
  after: [],
379
376
  onError: [],
380
377
  result: []
381
- };
382
- super({ ...base, ...config }, (c) => new VigorFetchInterceptors(c));
383
- this._base = base;
378
+ });
384
379
  }
385
- getBase() { return this._base; }
386
380
  before(...funcs) { return this._next({ before: [...this.getConfig().before, ...funcs.flat()] }); }
387
381
  after(...funcs) { return this._next({ after: [...this.getConfig().after, ...funcs.flat()] }); }
388
382
  onError(...funcs) { return this._next({ onError: [...this.getConfig().onError, ...funcs.flat()] }); }
389
383
  result(...funcs) { return this._next({ result: [...this.getConfig().result, ...funcs.flat()] }); }
390
384
  }
391
385
  class VigorFetch extends VigorStatus {
392
- _base;
393
- constructor(config) {
394
- const base = {
386
+ constructor(config = {}) {
387
+ super(config, {
395
388
  setting: new VigorFetchSettings().getBase(),
396
389
  retryConfig: new VigorRetry().getBase(),
397
390
  parseConfig: new VigorParse().getBase(),
398
391
  interceptors: new VigorFetchInterceptors().getBase(),
399
- };
400
- super({ ...base, ...config }, (c) => new VigorFetch(c), () => VigorRetryError);
401
- this._base = base;
392
+ });
402
393
  }
403
- getBase() { return this._base; }
404
394
  origin(str) { return this._next({ setting: { ...this._config.setting, origin: str } }); }
405
395
  path(...strs) { return this._next({ setting: { ...this._config.setting, path: [...this._config.setting.path, ...strs.flat()] } }); }
406
396
  query(obj) { return this._next({ setting: { ...this._config.setting, query: { ...this._config.setting.query, ...obj } } }); }
@@ -409,63 +399,48 @@ class VigorFetch extends VigorStatus {
409
399
  body(obj) { return this._next({ setting: { ...this._config.setting, body: obj } }); }
410
400
  options(obj) { return this._next({ setting: { ...this._config.setting, options: obj } }); }
411
401
  setting(func) {
412
- return this._next({
413
- setting: this._pipeSub(this._config.setting, VigorFetchSettings, func, "setting")
414
- });
402
+ return this._next({ setting: this._pipsub(this._config.setting, func, VigorFetchSettings) });
403
+ }
404
+ interceptors(func) {
405
+ return this._next({ interceptors: this._pipsub(this._config.interceptors, func, VigorFetchInterceptors) });
415
406
  }
416
407
  retryConfig(func) {
417
- return this._next({
418
- retryConfig: this._pipeSub(this._config.retryConfig, VigorRetry, func, "retryConfig")
419
- });
408
+ return this._next({ retryConfig: this._pipsub(this._config.retryConfig, func, VigorRetry) });
420
409
  }
421
410
  parseConfig(func) {
422
- return this._next({
423
- parseConfig: this._pipeSub(this._config.parseConfig, VigorParse, func, "parseConfig")
424
- });
411
+ return this._next({ parseConfig: this._pipsub(this._config.parseConfig, func, VigorParse) });
425
412
  }
426
413
  buildUrl(origin, path, query) {
427
414
  if (!origin)
428
- throw new VigorFetchError("buildUrl expects 'origin'", {
429
- type: "invalid_input", method: "buildUrl", data: {
430
- expected: "string", received: origin
415
+ throw new VigorFetchError("INVALID_URL", {
416
+ method: "buildUrl",
417
+ data: {
418
+ received: origin
431
419
  }
432
420
  });
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;
443
- }
421
+ const url = new URL(origin);
422
+ const segments = [
423
+ url.pathname,
424
+ ...path
425
+ ]
426
+ .flat()
427
+ .filter(Boolean)
428
+ .flatMap(p => p.split('/'))
429
+ .filter(Boolean);
430
+ url.pathname = '/' + segments.join('/');
431
+ const params = new URLSearchParams(url.search);
432
+ for (const [k, v] of Object.entries(query ?? {})) {
433
+ if (v == null)
434
+ continue;
435
+ if (Array.isArray(v)) {
436
+ v.forEach(i => params.append(k, String(i)));
444
437
  }
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
- });
438
+ else {
439
+ params.set(k, String(v));
456
440
  }
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 }
462
- });
463
441
  }
464
- }
465
- interceptors(func) {
466
- return this._next({
467
- interceptors: this._pipeSub(this._config.interceptors, VigorFetchInterceptors, func, "interceptors")
468
- });
442
+ url.search = params.toString();
443
+ return url.toString();
469
444
  }
470
445
  async request() {
471
446
  const config = this._config;
@@ -490,15 +465,21 @@ class VigorFetch extends VigorStatus {
490
465
  onError: [...config.interceptors.onError],
491
466
  result: [...config.interceptors.result]
492
467
  },
493
- runtime: {}
468
+ runtime: {
469
+ result: EMPTY
470
+ }
494
471
  };
495
472
  const throwError = (error) => { throw error; };
496
473
  try {
497
474
  ctx.runtime.unretrySet = new Set(ctx.setting.unretry);
498
475
  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
- } });
476
+ throw new VigorFetchError("INVALID_PROTOCOL", {
477
+ method: "request",
478
+ data: {
479
+ expected: ["http", "https", "data", "blob", "file", "about"],
480
+ received: ctx.setting.origin
481
+ }
482
+ });
502
483
  ctx.runtime.url = this.buildUrl(config.setting.origin, config.setting.path, config.setting.query);
503
484
  const isJson = Array.isArray(ctx.setting.body) || (!!ctx.setting.body && Object.getPrototypeOf(ctx.setting.body) === Object.prototype);
504
485
  ctx.runtime.baseOptions = {
@@ -519,9 +500,14 @@ class VigorFetch extends VigorStatus {
519
500
  const checkOk = async (ctx2, { throwError }) => {
520
501
  const result = ctx2.runtime.result;
521
502
  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 }
503
+ return throwError?.(new VigorFetchError("FETCH_ERROR", {
504
+ method: "request",
505
+ type: "fetch_error",
506
+ data: {
507
+ status: result.status,
508
+ statusText: result.statusText,
509
+ url: result.url
510
+ }
525
511
  }));
526
512
  };
527
513
  const handleBlacklist = (ctx2, { cancelRetry }) => {
@@ -567,96 +553,95 @@ class VigorFetch extends VigorStatus {
567
553
  for (const func of ctx.interceptors.onError) {
568
554
  await func(ctx, { setResult, throwError });
569
555
  }
570
- if (overrided && ctx.runtime.result !== undefined)
556
+ if (overrided && ctx.runtime.result !== EMPTY)
571
557
  return ctx.runtime.result;
572
- if (ctx.setting.default !== undefined)
558
+ if (ctx.setting.default !== EMPTY)
573
559
  return ctx.setting.default;
574
560
  throw error;
575
561
  }
576
562
  }
577
563
  }
578
564
  class VigorAllSettings extends VigorStatus {
579
- _base;
580
- constructor(config) {
581
- const base = {
565
+ constructor(config = {}) {
566
+ super(config, {
582
567
  concurrency: 5,
583
- jitter: 1000
584
- };
585
- super({ ...base, ...config }, (c) => new VigorAllSettings(c));
586
- this._base = base;
568
+ jitter: 1000,
569
+ onlySuccess: false
570
+ });
587
571
  }
588
- getBase() { return this._base; }
589
572
  concurrency(num) { return this._next({ concurrency: num }); }
590
573
  jitter(num) { return this._next({ jitter: num }); }
574
+ onlySuccess(bool) { return this._next({ onlySuccess: bool }); }
591
575
  }
592
576
  class VigorAllInterceptors extends VigorStatus {
593
- _base;
594
- constructor(config) {
595
- const base = {
577
+ constructor(config = {}) {
578
+ super(config, {
596
579
  before: [],
597
580
  after: [],
598
581
  onError: [],
599
582
  result: []
600
- };
601
- super({ ...base, ...config }, (c) => new VigorAllInterceptors(c));
602
- this._base = base;
583
+ });
603
584
  }
604
- getBase() { return this._base; }
605
585
  before(...funcs) { return this._next({ before: [...this.getConfig().before, ...funcs.flat()] }); }
606
586
  after(...funcs) { return this._next({ after: [...this.getConfig().after, ...funcs.flat()] }); }
607
587
  onError(...funcs) { return this._next({ onError: [...this.getConfig().onError, ...funcs.flat()] }); }
608
588
  result(...funcs) { return this._next({ result: [...this.getConfig().result, ...funcs.flat()] }); }
609
589
  }
610
590
  class VigorAll extends VigorStatus {
611
- _base;
612
- constructor(config) {
613
- const base = {
591
+ constructor(config = {}) {
592
+ super(config, {
614
593
  target: [],
615
594
  setting: new VigorAllSettings().getBase(),
616
595
  interceptors: new VigorAllInterceptors().getBase()
617
- };
618
- super({ ...base, ...config }, (c) => new VigorAll(c), () => VigorAllError);
619
- this._base = base;
596
+ });
597
+ }
598
+ _transfer(config) {
599
+ return new VigorAll({
600
+ ...this._config,
601
+ ...config
602
+ });
603
+ }
604
+ target(...funcs) {
605
+ return this._transfer({
606
+ target: funcs
607
+ });
620
608
  }
621
- getBase() { return this._base; }
622
- target(...funcs) { return this._next({ target: [...this._config.target, ...funcs.flat()] }); }
623
609
  setting(func) {
624
610
  return this._next({
625
- setting: this._pipeSub(this._config.setting, VigorAllSettings, func, "setting")
611
+ setting: this._pipsub(this._config.setting, func, VigorAllSettings)
626
612
  });
627
613
  }
628
614
  interceptors(func) {
629
615
  return this._next({
630
- interceptors: this._pipeSub(this._config.interceptors, VigorAllInterceptors, func, "interceptors")
616
+ interceptors: this._pipsub(this._config.interceptors, func, VigorAllInterceptors)
631
617
  });
632
618
  }
633
619
  async request() {
634
620
  const config = this._config;
635
621
  let ctx = {
636
- target: [...config.target],
637
622
  setting: { ...config.setting },
623
+ target: [...config.target],
638
624
  interceptors: {
639
625
  before: [...config.interceptors.before],
640
626
  after: [...config.interceptors.after],
641
627
  onError: [...config.interceptors.onError],
642
- result: [...config.interceptors.result]
628
+ result: [...config.interceptors.result],
643
629
  },
644
630
  runtime: {
645
631
  tasks: [],
646
- result: []
632
+ result: [],
647
633
  }
648
634
  };
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
- }
635
+ if (ctx.target.length == 0)
636
+ throw new VigorAllError("TARGET_MISSING", {
637
+ method: "request",
638
+ data: undefined
654
639
  });
655
640
  let active = 0;
656
641
  const queue = [];
657
642
  const runTask = async (task) => {
658
643
  await new Promise(resolve => {
659
- if (active < ctx.setting.concurrency) {
644
+ if (active < config.setting.concurrency) {
660
645
  active++;
661
646
  resolve();
662
647
  }
@@ -668,29 +653,42 @@ class VigorAll extends VigorStatus {
668
653
  }
669
654
  });
670
655
  const throwError = (error) => { throw error; };
656
+ let ctxTask = {
657
+ target: task,
658
+ runtime: {
659
+ result: EMPTY,
660
+ error: null,
661
+ jitter: VigorRetryBackoff.randomJitter(config.setting.jitter)
662
+ }
663
+ };
671
664
  try {
672
- await new Promise(resolve => setTimeout(resolve, calculateJitter(ctx.setting.jitter)));
673
- let res;
674
- for (const func of ctx.interceptors.before) {
675
- await func(ctx, { throwError });
665
+ await new Promise(resolve => setTimeout(resolve, ctxTask.runtime.jitter));
666
+ for (const func of config.interceptors.before) {
667
+ await func(ctxTask, { throwError });
676
668
  }
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 });
669
+ ctxTask.runtime.result = await task(ctxTask, {});
670
+ const setResult = (result) => ctxTask.runtime.result = result;
671
+ for (const func of config.interceptors.after) {
672
+ await func(ctxTask, { setResult, throwError });
681
673
  }
682
- return res;
674
+ if (ctxTask.runtime.result === EMPTY) {
675
+ throw new Error("Result not set");
676
+ }
677
+ return ctxTask.runtime.result;
683
678
  }
684
679
  catch (error) {
685
- let res;
680
+ ctxTask.runtime.error = error;
686
681
  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 });
682
+ const setResult = (result) => {
683
+ overrided = true;
684
+ return (ctxTask.runtime.result = result);
685
+ };
686
+ for (const func of config.interceptors.onError) {
687
+ await func(ctxTask, { setResult, throwError });
690
688
  }
691
- if (overrided && res !== undefined)
692
- return res;
693
- throw error;
689
+ if (overrided && ctxTask.runtime.result !== EMPTY)
690
+ return ctxTask.runtime.result;
691
+ throw ctxTask.runtime.error;
694
692
  }
695
693
  finally {
696
694
  active--;
@@ -701,26 +699,28 @@ class VigorAll extends VigorStatus {
701
699
  };
702
700
  ctx.runtime.tasks = ctx.target.map(task => runTask(task));
703
701
  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
702
+ const isFailed = Symbol("FAILED");
703
+ ctx.runtime.result = settled.map((res, idx) => {
704
+ if (res.status === "fulfilled")
705
+ return res.value;
706
+ if (ctx.setting.onlySuccess)
707
+ return isFailed;
708
+ return new VigorAllError("REQUEST_FAILED", {
709
+ method: "request",
710
+ data: {
711
+ index: idx,
712
+ error: res.reason
710
713
  }
711
714
  });
712
- });
715
+ }).filter(i => i !== isFailed);
713
716
  const setResult = (result) => ctx.runtime.result = result;
714
717
  const throwError = (error) => { throw error; };
715
- for (const func of ctx.interceptors.result) {
718
+ for (const func of config.interceptors.result) {
716
719
  await func(ctx, { setResult, throwError });
717
720
  }
718
721
  return ctx.runtime.result;
719
722
  }
720
723
  }
721
- function calculateJitter(jitter) {
722
- return jitter * (Math.random() * 2 - 1);
723
- }
724
724
  class Vigor {
725
725
  registry;
726
726
  constructor(config) {
@@ -754,8 +754,11 @@ class Vigor {
754
754
  fetch(origin) {
755
755
  return this.registry.VigorFetch.main().origin(origin);
756
756
  }
757
- all(tasks) {
758
- return this.registry.VigorAll.main().target(tasks.flat());
757
+ all(...args) {
758
+ const flatTasks = args.flat();
759
+ return this.registry.VigorAll
760
+ .main()
761
+ .target(...flatTasks);
759
762
  }
760
763
  parse(response) {
761
764
  return this.registry.VigorParse.main().target(response);