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