vigor-fetch 1.0.10 → 1.0.12

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