vigor-fetch 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,57 +8,126 @@
8
8
  - 📈 **Exponential Backoff & Jitter:** Prevents server hammering by increasing wait times with random variation.
9
9
  - ⚡ **Zero Dependencies:** Built using native Fetch API and AbortController.
10
10
  - 🎯 **Fully Type-Safe:** Written in TypeScript for excellent developer experience and auto-completion.
11
+ - 🚦 **Concurrency Control**: Execute bulk requests with a global limit and inter-request jitter using the VigorAll engine.
12
+ - 🎯 **Immutable Chaining**: Every configuration method returns a new instance, making it perfect for base templates and functional patterns.
11
13
 
12
14
  ## 📦 Installation
13
15
 
14
16
  npm install vigor-fetch
15
17
 
18
+ ## 🛠️ API References
19
+
20
+ 1. Vigor().fetch(origin, config)
21
+
22
+ | Method | Type | Default | Description
23
+ | :--- | :--- | :--- | :--- |
24
+ | .path(arg) | string | "" | Sets the endpoint path to be appended to the origin. |
25
+ | .query(arg) | Record<string, any> | {} | Appends key-value pairs as query parameters to the URL. |
26
+ | .headers(arg) | Record<string, string> | {} | Sets the HTTP request headers. |
27
+ | .body(arg) | any | null | Sets the request body |
28
+ | .offset(arg) | RequestInit | {} | Provides raw fetch options to be merged into the request. |
29
+ | .count(arg) | number | 5 | Specifies the maximum number of retry attempts for the request. |
30
+ | .max(arg) | number | 5000 | Sets the timeout (in ms) for each individual request attempt. |
31
+ | .wait(arg) | number | 10000 | Sets the maximum wait time (in ms) between retry attempts. |
32
+ | .backoff(arg) | number | 1.3 | The multiplier used for exponential backoff between retries. |
33
+ | .jitter(arg) | number | 500 | Adds a random delay (up to this value in ms) to prevent thundering herd issues. |
34
+ | .unretry(arg[]) | number[] | [400, 401, 403, 404, 405, 413, 422] | List of HTTP status codes that should NOT trigger a retry. |
35
+ | .retryHeader(...arg) | string[] | ['retry-after', ...] | Custom headers to check for server-defined retry delays |
36
+ | .original(bool) | boolean | false | If true, returns the raw Response object instead of parsed data. |
37
+ | .parse(key) | keyof Response | null | Forces the use of a specific Response method (e.g., 'json', 'blob') for parsing. |
38
+ | .beforeRequest(...hooks) | Function[] | [] | Hooks executed to modify request options before the fetch occurs. |
39
+ | .afterRequest(...hooks) | Function[] | [] | Hooks executed immediately after the fetch, before the status check. |
40
+ | .beforeResponse(...hooks) | Function[] | [] | Hooks executed on the Response object before parsing the body. |
41
+ | .afterResponse(...hooks) | Function[] | [] | Hooks executed on the parsed data before returning the final result. |
42
+ | .onError(...hooks) | Function[] | [] | Hooks to handle errors; can recover from error by returning a value. |
43
+ | .request() | Promise<T> | - | Executes the request logic including retries and hooks. |
44
+
45
+ 2. Vigor().all(config)
46
+ | Method | Type | Default | Description
47
+ | :--- | :--- | :--- | :--- |
48
+ | .limit(arg) | number | 10 | Maximum number of concurrent promises running at once. |
49
+ | .jitter(arg) | number | 1000 | Random delay (ms) applied before each task starts. |
50
+ | .promises(...args) | Function[] | [] | Adds functions that return promises to the execution queue. |
51
+ | .request() | Promise<any[]> | - | Executes all tasks with concurrency control and returns results. |
52
+
16
53
  ## 🚀 Quick Start
17
54
 
18
- ```
55
+ ```javascript
19
56
 
20
57
  import Vigor from 'vigor-fetch';
21
58
 
22
- const api = new Vigor("https://api.example.com")
23
- .path("/v1/data")
59
+ const vigor = new Vigor();
60
+
61
+ const data = await vigor.fetch("https://api.example.com")
62
+ .path("/v1/users")
24
63
  .method("POST")
25
- .headers({ "X-API-KEY": "123" })
26
- .query({ limit: 10, page: 1 })
64
+ .headers({ "Authorization": "Bearer TOKEN" })
27
65
  .body({ name: "Uav1010" })
28
- .count(5)
29
- .max(5000)
30
- .wait(10000)
31
- .backoff(1.5)
32
- .jitter(500)
33
- .unretry([401, 404])
34
- .parse("json")
35
- .beforeRequest((opt) => {
36
- console.log("Sending...");
37
- })
38
- .afterResponse((res) => {
39
- return { ...res, timestamp: Date.now() };
40
- });
66
+ .count(5) // Retry up to 5 times
67
+ .backoff(1.5) // Multiply wait time by 1.5x each failure
68
+ .parse("json") // Auto-parse response as JSON
69
+ .request();
41
70
 
42
- const data = await api.request();
71
+ ```
72
+
73
+ ## 🛠️ Advanced Patterns
74
+
75
+ 1. Request Templates
76
+
77
+ ```javascript
78
+
79
+ import Vigor from 'vigor-fetch';
80
+
81
+ const vigor = new Vigor();
82
+
83
+ const apiClient = vigor.fetch("https://api.myapp.com")
84
+ .headers({ "Content-Type": "application/json" })
85
+ .unretry([401, 403, 404]) // Don't retry on these statuses
86
+ .max(3000); // 3s timeout per attempt
87
+
88
+ const user = await apiClient.path("/me").request();
89
+ const settings = await apiClient.path("/settings").request();
43
90
 
44
91
  ```
45
92
 
46
- ## 🛠️ API Reference
47
-
48
- | Method | Type | Description |
49
- | :--- | :--- | :--- |
50
- | .path(string) | string | Appends path to origin. |
51
- | .method(string) | string | Sets HTTP method (GET, POST, etc). |
52
- | .query(object) | Record | Key-value pairs for URL search params. |
53
- | .headers(object) | Record | Custom HTTP headers. |
54
- | .body(any) | any | Request payload (auto-detects JSON). |
55
- | .count(number) | number | Max retry attempts (Default: 5). |
56
- | .max(ms) | number | Timeout for each fetch attempt (Default: 5000ms). |
57
- | .wait(ms) | number | Max delay allowed. If exceeded, throws error. |
58
- | .backoff(number) | number | Retry interval multiplier (Default: 1.3). |
59
- | .jitter(ms) | number | Randomness to avoid thundering herd (Default: 500ms). |
60
- | .unretry(codes[]) | number[] | Status codes that stop retrying (e.g. 404). |
61
- | .original(bool) | boolean | If true, returns raw Response object. |
62
- | .parse(type) | string | json, text, blob, arrayBuffer, formData. |
63
- | .beforeRequest() | function | Async hook to modify options before fetch. |
64
- | .afterResponse() | function | Async hook to transform data after fetch. |
93
+ 2. Batch Processing with Concurrency Limit
94
+
95
+ ```javascript
96
+
97
+ import Vigor from 'vigor-fetch';
98
+
99
+ const vigor = new Vigor();
100
+
101
+ const tasks = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(id => () =>
102
+ vigor.fetch("https://api.com").path(`/data/${id}`).request()
103
+ );
104
+
105
+ const results = await vigor.all()
106
+ .limit(3) // Max 3 concurrent requests
107
+ .jitter(500) // Add up to 500ms random delay between starts
108
+ .promises(...tasks)
109
+ .request();
110
+
111
+ ```
112
+
113
+ 3. Middleware & Hooks
114
+
115
+ ```javascript
116
+
117
+ import Vigor from 'vigor-fetch';
118
+
119
+ const vigor = new Vigor();
120
+
121
+ const api = vigor.fetch("https://api.com")
122
+ .beforeRequest((opt) => {
123
+ opt.headers = { ...opt.headers, "X-Timestamp": Date.now().toString() };
124
+ })
125
+ .afterResponse((data) => {
126
+ return { ...data, receivedAt: new Date() }; // Transform final result
127
+ })
128
+ .onError((err) => {
129
+ if (err.status === 404) return { error: "Not Found", fallback: true };
130
+ throw err; // Continue throwing if not handled
131
+ });
132
+
133
+ ```
@@ -0,0 +1,75 @@
1
+ interface VigorErrorOptions {
2
+ url?: string | null;
3
+ status?: number;
4
+ message?: string;
5
+ data?: any;
6
+ }
7
+ interface VigorFetchConfig {
8
+ path: string;
9
+ method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD" | null;
10
+ offset: RequestInit;
11
+ headers: Record<string, string>;
12
+ body: any;
13
+ count: number;
14
+ max: number;
15
+ wait: number;
16
+ backoff: number;
17
+ unretry: Set<number>;
18
+ retryHeader: string[];
19
+ original: boolean;
20
+ parse: keyof Response | null;
21
+ beforeRequest: Array<(opt: RequestInit) => Promise<Partial<RequestInit> | void> | Partial<RequestInit> | void>;
22
+ afterRequest: Array<(res: Response) => Promise<Response | void> | Response | void>;
23
+ beforeResponse: Array<(res: Response) => Promise<Response> | Response>;
24
+ afterResponse: Array<(data: any) => Promise<any> | any>;
25
+ onError: Array<(err: any) => Promise<any> | any>;
26
+ query: Record<string, any>;
27
+ jitter: number;
28
+ }
29
+ declare class VigorFetch<T = any> {
30
+ private _origin;
31
+ private _config;
32
+ constructor(origin: string, config?: VigorFetchConfig);
33
+ private _next;
34
+ path(arg: string): VigorFetch<T>;
35
+ method(arg: VigorFetchConfig['method']): VigorFetch<T>;
36
+ offset(arg: RequestInit): VigorFetch<T>;
37
+ headers(arg: Record<string, string>): VigorFetch<T>;
38
+ body(arg: any): VigorFetch<T>;
39
+ count(arg: number): VigorFetch<T>;
40
+ max(arg: number): VigorFetch<T>;
41
+ wait(arg: number): VigorFetch<T>;
42
+ backoff(arg: number): VigorFetch<T>;
43
+ unretry(arg: number[]): VigorFetch<T>;
44
+ retryHeader(...arg: string[]): VigorFetch<T>;
45
+ original(arg: boolean): VigorFetch<T>;
46
+ parse(arg: keyof Response): VigorFetch<T>;
47
+ query(arg: Record<string, any>): VigorFetch<T>;
48
+ jitter(arg: number): VigorFetch<T>;
49
+ beforeRequest(...arg: VigorFetchConfig['beforeRequest']): VigorFetch<T>;
50
+ afterRequest(...arg: VigorFetchConfig['afterRequest']): VigorFetch<T>;
51
+ beforeResponse(...arg: VigorFetchConfig['beforeResponse']): VigorFetch<T>;
52
+ afterResponse(...arg: VigorFetchConfig['afterResponse']): VigorFetch<T>;
53
+ onError(...arg: VigorFetchConfig['onError']): VigorFetch<T>;
54
+ request(): Promise<T>;
55
+ }
56
+ interface VigorAllConfig {
57
+ limit: number;
58
+ jitter: number;
59
+ promises: Array<() => Promise<any>>;
60
+ }
61
+ declare class VigorAll {
62
+ private _config;
63
+ constructor(config?: VigorAllConfig);
64
+ private _next;
65
+ limit(arg: number): VigorAll;
66
+ jitter(arg: number): VigorAll;
67
+ promises(...args: Array<() => Promise<any>>): VigorAll;
68
+ request(): Promise<any[]>;
69
+ }
70
+ declare class Vigor {
71
+ fetch<T = any>(origin: string, config?: VigorFetchConfig): VigorFetch<T>;
72
+ all(config?: VigorAllConfig): VigorAll;
73
+ }
74
+
75
+ export { type VigorAllConfig, type VigorErrorOptions, type VigorFetchConfig, Vigor as default };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,12 @@
1
- interface VigorConfig {
1
+ interface VigorErrorOptions {
2
+ url?: string | null;
3
+ status?: number;
4
+ message?: string;
5
+ data?: any;
6
+ }
7
+ interface VigorFetchConfig {
2
8
  path: string;
3
- method: string | null;
9
+ method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD" | null;
4
10
  offset: RequestInit;
5
11
  headers: Record<string, string>;
6
12
  body: any;
@@ -9,34 +15,61 @@ interface VigorConfig {
9
15
  wait: number;
10
16
  backoff: number;
11
17
  unretry: Set<number>;
18
+ retryHeader: string[];
12
19
  original: boolean;
13
20
  parse: keyof Response | null;
14
- beforeRequest: ((option: RequestInit) => Promise<RequestInit | void> | RequestInit | void)[];
15
- afterResponse: ((res: any) => Promise<any> | any)[];
16
- query: Record<string, string | number | boolean | null | undefined>;
21
+ beforeRequest: Array<(opt: RequestInit) => Promise<Partial<RequestInit> | void> | Partial<RequestInit> | void>;
22
+ afterRequest: Array<(res: Response) => Promise<Response | void> | Response | void>;
23
+ beforeResponse: Array<(res: Response) => Promise<Response> | Response>;
24
+ afterResponse: Array<(data: any) => Promise<any> | any>;
25
+ onError: Array<(err: any) => Promise<any> | any>;
26
+ query: Record<string, any>;
17
27
  jitter: number;
18
28
  }
19
- export default class Vigor {
29
+ declare class VigorFetch<T = any> {
20
30
  private _origin;
21
31
  private _config;
22
- constructor(origin: string, config?: VigorConfig);
32
+ constructor(origin: string, config?: VigorFetchConfig);
23
33
  private _next;
24
- path(arg: string): Vigor;
25
- method(arg: string): Vigor;
26
- offset(arg: RequestInit): Vigor;
27
- headers(arg: Record<string, string>): Vigor;
28
- body(arg: any): Vigor;
29
- count(arg: number): Vigor;
30
- max(arg: number): Vigor;
31
- wait(arg: number): Vigor;
32
- backoff(arg: number): Vigor;
33
- unretry(arg: number[]): Vigor;
34
- original(arg: boolean): Vigor;
35
- beforeRequest(...arg: VigorConfig['beforeRequest']): Vigor;
36
- afterResponse(...arg: VigorConfig['afterResponse']): Vigor;
37
- parse(arg: VigorConfig['parse']): Vigor;
38
- query(arg: VigorConfig['query']): Vigor;
39
- jitter(arg: number): Vigor;
40
- request<T = any>(): Promise<T>;
34
+ path(arg: string): VigorFetch<T>;
35
+ method(arg: VigorFetchConfig['method']): VigorFetch<T>;
36
+ offset(arg: RequestInit): VigorFetch<T>;
37
+ headers(arg: Record<string, string>): VigorFetch<T>;
38
+ body(arg: any): VigorFetch<T>;
39
+ count(arg: number): VigorFetch<T>;
40
+ max(arg: number): VigorFetch<T>;
41
+ wait(arg: number): VigorFetch<T>;
42
+ backoff(arg: number): VigorFetch<T>;
43
+ unretry(arg: number[]): VigorFetch<T>;
44
+ retryHeader(...arg: string[]): VigorFetch<T>;
45
+ original(arg: boolean): VigorFetch<T>;
46
+ parse(arg: keyof Response): VigorFetch<T>;
47
+ query(arg: Record<string, any>): VigorFetch<T>;
48
+ jitter(arg: number): VigorFetch<T>;
49
+ beforeRequest(...arg: VigorFetchConfig['beforeRequest']): VigorFetch<T>;
50
+ afterRequest(...arg: VigorFetchConfig['afterRequest']): VigorFetch<T>;
51
+ beforeResponse(...arg: VigorFetchConfig['beforeResponse']): VigorFetch<T>;
52
+ afterResponse(...arg: VigorFetchConfig['afterResponse']): VigorFetch<T>;
53
+ onError(...arg: VigorFetchConfig['onError']): VigorFetch<T>;
54
+ request(): Promise<T>;
55
+ }
56
+ interface VigorAllConfig {
57
+ limit: number;
58
+ jitter: number;
59
+ promises: Array<() => Promise<any>>;
60
+ }
61
+ declare class VigorAll {
62
+ private _config;
63
+ constructor(config?: VigorAllConfig);
64
+ private _next;
65
+ limit(arg: number): VigorAll;
66
+ jitter(arg: number): VigorAll;
67
+ promises(...args: Array<() => Promise<any>>): VigorAll;
68
+ request(): Promise<any[]>;
69
+ }
70
+ declare class Vigor {
71
+ fetch<T = any>(origin: string, config?: VigorFetchConfig): VigorFetch<T>;
72
+ all(config?: VigorAllConfig): VigorAll;
41
73
  }
42
- export {};
74
+
75
+ export { type VigorAllConfig, type VigorErrorOptions, type VigorFetchConfig, Vigor as default };
package/dist/index.js CHANGED
@@ -1,158 +1,282 @@
1
1
  "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- class VigorError extends Error {
4
- constructor(text, { url, status, message, data = null }) {
5
- super(text);
6
- this.name = "VigorError";
7
- this.url = url;
8
- this.status = status;
9
- this.message = message;
10
- this.data = data;
11
- }
12
- }
13
- class Vigor {
14
- constructor(origin, config) {
15
- this._origin = origin;
16
- this._config = config || {
17
- path: "",
18
- method: null,
19
- offset: {},
20
- headers: {},
21
- body: null,
22
- count: 5,
23
- max: 5000,
24
- wait: 10000,
25
- backoff: 1.3,
26
- unretry: new Set([400, 401, 403, 404, 405, 413, 422]),
27
- original: false,
28
- parse: null,
29
- beforeRequest: [],
30
- afterResponse: [],
31
- query: {},
32
- jitter: 500
33
- };
34
- }
35
- _next(changes) {
36
- return new Vigor(this._origin, { ...this._config, ...changes });
37
- }
38
- path(arg) { return this._next({ path: arg }); }
39
- method(arg) { return this._next({ method: arg }); }
40
- offset(arg) { return this._next({ offset: arg }); }
41
- headers(arg) { return this._next({ headers: arg }); }
42
- body(arg) { return this._next({ body: arg }); }
43
- count(arg) { return this._next({ count: arg }); }
44
- max(arg) { return this._next({ max: arg }); }
45
- wait(arg) { return this._next({ wait: arg }); }
46
- backoff(arg) { return this._next({ backoff: arg }); }
47
- unretry(arg) { return this._next({ unretry: new Set(arg) }); }
48
- original(arg) { return this._next({ original: arg }); }
49
- beforeRequest(...arg) {
50
- return this._next({ beforeRequest: [...this._config.beforeRequest, ...arg] });
51
- }
52
- afterResponse(...arg) {
53
- return this._next({ afterResponse: [...this._config.afterResponse, ...arg] });
54
- }
55
- parse(arg) { return this._next({ parse: arg }); }
56
- query(arg) { return this._next({ query: { ...this._config.query, ...arg } }); }
57
- jitter(arg) { return this._next({ jitter: arg }); }
58
- async request() {
59
- const { path, method, offset, headers, body, count, max, wait, backoff, unretry, original, beforeRequest, afterResponse, parse, query, jitter } = this._config;
60
- if (!/^(https?|data|blob|file|about):\/\//.test(this._origin)) {
61
- throw new VigorError(`[vigor] ${this._origin} >> Invalid Protocol`, {
62
- url: this._origin, status: 0, message: "Invalid Protocol"
63
- });
64
- }
65
- const urlObj = new URL(path.replace(/^\//, ""), this._origin + "/");
66
- Object.entries(query).forEach(([key, value]) => {
67
- if (value !== null && value !== undefined) {
68
- urlObj.searchParams.append(key, String(value));
69
- }
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ default: () => index_default
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+ var VigorError = class extends Error {
27
+ constructor(text, { url = null, status = 0, message, data = null }) {
28
+ super(text);
29
+ this.name = "VigorError";
30
+ this.url = url;
31
+ this.status = status;
32
+ this.message = message || text;
33
+ this.data = data;
34
+ }
35
+ };
36
+ var VigorFetch = class _VigorFetch {
37
+ constructor(origin, config) {
38
+ this._origin = origin;
39
+ this._config = config || {
40
+ path: "",
41
+ method: null,
42
+ offset: {},
43
+ headers: {},
44
+ body: null,
45
+ count: 5,
46
+ max: 5e3,
47
+ wait: 1e4,
48
+ backoff: 1.3,
49
+ unretry: /* @__PURE__ */ new Set([400, 401, 403, 404, 405, 413, 422]),
50
+ retryHeader: ["retry-after", "ratelimit-reset", "x-ratelimit-reset", "x-retry-after", "x-amz-retry-after", "chrome-proxy-next-link"],
51
+ original: false,
52
+ parse: null,
53
+ query: {},
54
+ jitter: 500,
55
+ beforeRequest: [],
56
+ afterRequest: [],
57
+ beforeResponse: [],
58
+ afterResponse: [],
59
+ onError: []
60
+ };
61
+ }
62
+ _next(changes) {
63
+ return new _VigorFetch(this._origin, { ...this._config, ...changes });
64
+ }
65
+ path(arg) {
66
+ return this._next({ path: arg });
67
+ }
68
+ method(arg) {
69
+ return this._next({ method: arg });
70
+ }
71
+ offset(arg) {
72
+ return this._next({ offset: arg });
73
+ }
74
+ headers(arg) {
75
+ return this._next({ headers: arg });
76
+ }
77
+ body(arg) {
78
+ return this._next({ body: arg });
79
+ }
80
+ count(arg) {
81
+ return this._next({ count: arg });
82
+ }
83
+ max(arg) {
84
+ return this._next({ max: arg });
85
+ }
86
+ wait(arg) {
87
+ return this._next({ wait: arg });
88
+ }
89
+ backoff(arg) {
90
+ return this._next({ backoff: arg });
91
+ }
92
+ unretry(arg) {
93
+ return this._next({ unretry: new Set(arg) });
94
+ }
95
+ retryHeader(...arg) {
96
+ return this._next({ retryHeader: [...this._config.retryHeader, ...arg] });
97
+ }
98
+ original(arg) {
99
+ return this._next({ original: arg });
100
+ }
101
+ parse(arg) {
102
+ return this._next({ parse: arg });
103
+ }
104
+ query(arg) {
105
+ return this._next({ query: { ...this._config.query, ...arg } });
106
+ }
107
+ jitter(arg) {
108
+ return this._next({ jitter: arg });
109
+ }
110
+ beforeRequest(...arg) {
111
+ return this._next({ beforeRequest: [...this._config.beforeRequest, ...arg] });
112
+ }
113
+ afterRequest(...arg) {
114
+ return this._next({ afterRequest: [...this._config.afterRequest, ...arg] });
115
+ }
116
+ beforeResponse(...arg) {
117
+ return this._next({ beforeResponse: [...this._config.beforeResponse, ...arg] });
118
+ }
119
+ afterResponse(...arg) {
120
+ return this._next({ afterResponse: [...this._config.afterResponse, ...arg] });
121
+ }
122
+ onError(...arg) {
123
+ return this._next({ onError: [...this._config.onError, ...arg] });
124
+ }
125
+ async request() {
126
+ const {
127
+ path,
128
+ method,
129
+ offset,
130
+ headers,
131
+ body,
132
+ query,
133
+ count,
134
+ max,
135
+ wait,
136
+ backoff,
137
+ unretry,
138
+ jitter,
139
+ original,
140
+ parse,
141
+ retryHeader,
142
+ beforeRequest,
143
+ afterRequest,
144
+ beforeResponse,
145
+ afterResponse,
146
+ onError
147
+ } = this._config;
148
+ try {
149
+ if (!/^(https?|data|blob|file|about):\/\//.test(this._origin)) {
150
+ throw new VigorError(`[vigor] ${this._origin} >> Invalid Protocol`, {
151
+ url: this._origin,
152
+ status: 0,
153
+ message: "Invalid Protocol"
70
154
  });
71
- const url = urlObj.href;
72
- const isJson = Array.isArray(body) || (!!body && Object.getPrototypeOf(body) === Object.prototype);
73
- const waitTimeout = (time) => new Promise(resolve => setTimeout(resolve, time));
74
- let option = {
75
- ...offset,
76
- method: method || (body ? "POST" : "GET"),
77
- headers: {
78
- ...(isJson && { "Content-Type": "application/json" }),
79
- ...headers
80
- },
81
- ...(body && { body: isJson ? JSON.stringify(body) : body }),
82
- };
83
- for (const hook of beforeRequest) {
84
- const modified = await hook(option);
85
- option = { ...option, ...(modified || {}) };
155
+ }
156
+ const urlObj = new URL(path.replace(/^\//, ""), this._origin + "/");
157
+ Object.entries(query).forEach(([key, value]) => {
158
+ if (value !== null && value !== void 0) urlObj.searchParams.append(key, String(value));
159
+ });
160
+ const url = urlObj.href;
161
+ const isJson = Array.isArray(body) || !!body && Object.getPrototypeOf(body) === Object.prototype;
162
+ const waitTimeout = (time) => new Promise((resolve) => setTimeout(resolve, time));
163
+ let option = {
164
+ ...offset,
165
+ method: method || (body ? "POST" : "GET"),
166
+ headers: { ...isJson && { "Content-Type": "application/json" }, ...headers },
167
+ ...body && { body: isJson ? JSON.stringify(body) : body }
168
+ };
169
+ for (const hook of beforeRequest) {
170
+ const modified = await hook(option);
171
+ if (modified) option = { ...option, ...modified };
172
+ }
173
+ let req;
174
+ for (let i = 0; i < count; i++) {
175
+ const controller = new AbortController();
176
+ const abort = setTimeout(() => controller.abort(), max);
177
+ option.signal = controller.signal;
178
+ try {
179
+ req = await fetch(url, option);
180
+ for (const hook of afterRequest) {
181
+ req = await hook(req) || req;
182
+ }
183
+ if (req.ok) {
184
+ clearTimeout(abort);
185
+ break;
186
+ }
187
+ } catch (error) {
188
+ clearTimeout(abort);
189
+ if (i === count - 1) throw new VigorError(`[vigor] ${url} >> Network Error`, { url, status: 0, message: "Network Error" });
190
+ } finally {
191
+ clearTimeout(abort);
86
192
  }
87
- let req;
88
- for (let i = 0; i < count; i++) {
89
- const controller = new AbortController();
90
- const abort = setTimeout(() => controller.abort(), max);
91
- option.signal = controller.signal;
92
- try {
93
- req = await fetch(url, option);
94
- if (req.ok) {
95
- clearTimeout(abort);
96
- break;
97
- }
98
- }
99
- catch (error) {
100
- clearTimeout(abort);
101
- if (i === count - 1)
102
- throw new VigorError(`[vigor] ${url} >> Network Error`, {
103
- url, status: 0, message: "Network Error"
104
- });
105
- }
106
- finally {
107
- clearTimeout(abort);
108
- }
109
- if (req) {
110
- const status = req.status;
111
- if (unretry.has(status))
112
- throw new VigorError(`[vigor] ${url} >> Unretry ${status}`, {
113
- url, status, message: "Unretry", data: status
114
- });
115
- const basic = Math.min(Math.pow(backoff, i) * 1000, wait) + Math.random() * jitter;
116
- if (status === 429) {
117
- const retryHeaders = ["retry-after", "ratelimit-reset", "x-ratelimit-reset", "x-retry-after", "x-amz-retry-after", "chrome-proxy-next-link"];
118
- const retryAfter = retryHeaders.map(h => req?.headers.get(h)).find(Boolean);
119
- const delay = (Number(retryAfter) * 1000 || (new Date(retryAfter).getTime() - Date.now()));
120
- const parsedDelay = Math.max(0, delay) + Math.random() * jitter;
121
- if (parsedDelay > wait)
122
- throw new VigorError(`[vigor] ${url} >> Timeouted ${parsedDelay}ms`, {
123
- url, status, message: "Timeouted", data: parsedDelay
124
- });
125
- await waitTimeout(parsedDelay || basic);
126
- }
127
- else {
128
- await waitTimeout(basic);
129
- }
130
- }
193
+ if (req) {
194
+ const status = req.status;
195
+ if (unretry.has(status)) throw new VigorError(`[vigor] ${url} >> Unretry ${status}`, { url, status, message: "Unretry", data: status });
196
+ const basic = Math.min(Math.pow(backoff, i) * 1e3, wait) + Math.random() * jitter;
197
+ if (status === 429) {
198
+ const rHeader = retryHeader.map((h) => req?.headers.get(h)).find(Boolean);
199
+ const delay = rHeader ? isNaN(Number(rHeader)) ? new Date(rHeader).getTime() - Date.now() : Number(rHeader) * 1e3 : 0;
200
+ const parsedDelay = Math.max(0, delay) + Math.random() * jitter;
201
+ if (parsedDelay > wait) throw new VigorError(`[vigor] ${url} >> Timeouted ${parsedDelay}ms`, { url, status, message: "Timeouted", data: parsedDelay });
202
+ await waitTimeout(parsedDelay || basic);
203
+ } else {
204
+ await waitTimeout(basic);
205
+ }
131
206
  }
132
- if (!req || !req.ok)
133
- throw new VigorError(`[vigor] ${url} >> Failed`, {
134
- url, status: req?.status || 0, message: "Failed"
135
- });
136
- let res = await (async () => {
137
- if (original)
138
- return req;
139
- if (parse)
140
- return await req[parse]();
141
- const contentType = req.headers.get("Content-Type") || "";
142
- if (/json/.test(contentType))
143
- return await req.json();
144
- if (/(image|video|audio|pdf)/.test(contentType))
145
- return await req.blob();
146
- if (/octet-stream/.test(contentType))
147
- return await req.arrayBuffer();
148
- if (/form-data/.test(contentType))
149
- return await req.formData();
150
- return await req.text();
151
- })();
152
- for (const hook of afterResponse) {
153
- res = await hook(res);
207
+ }
208
+ if (!req) throw new Error("No response");
209
+ let currentReq = req;
210
+ for (const hook of beforeResponse) {
211
+ currentReq = await hook(currentReq);
212
+ }
213
+ if (!currentReq.ok) throw new VigorError(`[vigor] ${url} >> Failed`, { url, status: currentReq.status, message: "Failed" });
214
+ let res = await (async () => {
215
+ if (original) return currentReq;
216
+ if (parse) {
217
+ const target = currentReq[parse];
218
+ return typeof target === "function" ? await target.call(currentReq) : target;
154
219
  }
155
- return res;
220
+ const contentType = currentReq.headers.get("Content-Type") || "";
221
+ if (/json/.test(contentType)) return await currentReq.json();
222
+ if (/(image|video|audio|pdf)/.test(contentType)) return await currentReq.blob();
223
+ return await currentReq.text();
224
+ })();
225
+ for (const hook of afterResponse) {
226
+ res = await hook(res);
227
+ }
228
+ return res;
229
+ } catch (error) {
230
+ let currentError = error;
231
+ for (const hook of onError) {
232
+ const result = await hook(currentError);
233
+ if (result !== void 0 && !(result instanceof Error)) return result;
234
+ currentError = result || currentError;
235
+ }
236
+ throw currentError;
237
+ }
238
+ }
239
+ };
240
+ var VigorAll = class _VigorAll {
241
+ constructor(config) {
242
+ this._config = config || { limit: 10, jitter: 1e3, promises: [] };
243
+ }
244
+ _next(changes) {
245
+ return new _VigorAll({ ...this._config, ...changes });
246
+ }
247
+ limit(arg) {
248
+ return this._next({ limit: arg });
249
+ }
250
+ jitter(arg) {
251
+ return this._next({ jitter: arg });
252
+ }
253
+ promises(...args) {
254
+ return this._next({ promises: [...this._config.promises, ...args] });
255
+ }
256
+ async request() {
257
+ const { limit, jitter, promises } = this._config;
258
+ const results = [];
259
+ const executing = /* @__PURE__ */ new Set();
260
+ for (const task of promises) {
261
+ const p = Promise.resolve().then(() => new Promise((res) => setTimeout(res, Math.random() * jitter))).then(() => task());
262
+ results.push(p);
263
+ executing.add(p);
264
+ p.finally(() => executing.delete(p));
265
+ if (executing.size >= limit) await Promise.race(executing);
156
266
  }
157
- }
158
- exports.default = Vigor;
267
+ const ready = await Promise.allSettled(results);
268
+ return ready.map((i) => {
269
+ if (i.status === "fulfilled") return i.value;
270
+ return i.reason instanceof VigorError ? i.reason : new VigorError(i.reason?.message || "Unknown", { message: i.reason?.message || "Unknown" });
271
+ });
272
+ }
273
+ };
274
+ var Vigor = class {
275
+ fetch(origin, config) {
276
+ return new VigorFetch(origin, config);
277
+ }
278
+ all(config) {
279
+ return new VigorAll(config);
280
+ }
281
+ };
282
+ var index_default = Vigor;
package/dist/index.mjs ADDED
@@ -0,0 +1,261 @@
1
+ // src/index.ts
2
+ var VigorError = class extends Error {
3
+ constructor(text, { url = null, status = 0, message, data = null }) {
4
+ super(text);
5
+ this.name = "VigorError";
6
+ this.url = url;
7
+ this.status = status;
8
+ this.message = message || text;
9
+ this.data = data;
10
+ }
11
+ };
12
+ var VigorFetch = class _VigorFetch {
13
+ constructor(origin, config) {
14
+ this._origin = origin;
15
+ this._config = config || {
16
+ path: "",
17
+ method: null,
18
+ offset: {},
19
+ headers: {},
20
+ body: null,
21
+ count: 5,
22
+ max: 5e3,
23
+ wait: 1e4,
24
+ backoff: 1.3,
25
+ unretry: /* @__PURE__ */ new Set([400, 401, 403, 404, 405, 413, 422]),
26
+ retryHeader: ["retry-after", "ratelimit-reset", "x-ratelimit-reset", "x-retry-after", "x-amz-retry-after", "chrome-proxy-next-link"],
27
+ original: false,
28
+ parse: null,
29
+ query: {},
30
+ jitter: 500,
31
+ beforeRequest: [],
32
+ afterRequest: [],
33
+ beforeResponse: [],
34
+ afterResponse: [],
35
+ onError: []
36
+ };
37
+ }
38
+ _next(changes) {
39
+ return new _VigorFetch(this._origin, { ...this._config, ...changes });
40
+ }
41
+ path(arg) {
42
+ return this._next({ path: arg });
43
+ }
44
+ method(arg) {
45
+ return this._next({ method: arg });
46
+ }
47
+ offset(arg) {
48
+ return this._next({ offset: arg });
49
+ }
50
+ headers(arg) {
51
+ return this._next({ headers: arg });
52
+ }
53
+ body(arg) {
54
+ return this._next({ body: arg });
55
+ }
56
+ count(arg) {
57
+ return this._next({ count: arg });
58
+ }
59
+ max(arg) {
60
+ return this._next({ max: arg });
61
+ }
62
+ wait(arg) {
63
+ return this._next({ wait: arg });
64
+ }
65
+ backoff(arg) {
66
+ return this._next({ backoff: arg });
67
+ }
68
+ unretry(arg) {
69
+ return this._next({ unretry: new Set(arg) });
70
+ }
71
+ retryHeader(...arg) {
72
+ return this._next({ retryHeader: [...this._config.retryHeader, ...arg] });
73
+ }
74
+ original(arg) {
75
+ return this._next({ original: arg });
76
+ }
77
+ parse(arg) {
78
+ return this._next({ parse: arg });
79
+ }
80
+ query(arg) {
81
+ return this._next({ query: { ...this._config.query, ...arg } });
82
+ }
83
+ jitter(arg) {
84
+ return this._next({ jitter: arg });
85
+ }
86
+ beforeRequest(...arg) {
87
+ return this._next({ beforeRequest: [...this._config.beforeRequest, ...arg] });
88
+ }
89
+ afterRequest(...arg) {
90
+ return this._next({ afterRequest: [...this._config.afterRequest, ...arg] });
91
+ }
92
+ beforeResponse(...arg) {
93
+ return this._next({ beforeResponse: [...this._config.beforeResponse, ...arg] });
94
+ }
95
+ afterResponse(...arg) {
96
+ return this._next({ afterResponse: [...this._config.afterResponse, ...arg] });
97
+ }
98
+ onError(...arg) {
99
+ return this._next({ onError: [...this._config.onError, ...arg] });
100
+ }
101
+ async request() {
102
+ const {
103
+ path,
104
+ method,
105
+ offset,
106
+ headers,
107
+ body,
108
+ query,
109
+ count,
110
+ max,
111
+ wait,
112
+ backoff,
113
+ unretry,
114
+ jitter,
115
+ original,
116
+ parse,
117
+ retryHeader,
118
+ beforeRequest,
119
+ afterRequest,
120
+ beforeResponse,
121
+ afterResponse,
122
+ onError
123
+ } = this._config;
124
+ try {
125
+ if (!/^(https?|data|blob|file|about):\/\//.test(this._origin)) {
126
+ throw new VigorError(`[vigor] ${this._origin} >> Invalid Protocol`, {
127
+ url: this._origin,
128
+ status: 0,
129
+ message: "Invalid Protocol"
130
+ });
131
+ }
132
+ const urlObj = new URL(path.replace(/^\//, ""), this._origin + "/");
133
+ Object.entries(query).forEach(([key, value]) => {
134
+ if (value !== null && value !== void 0) urlObj.searchParams.append(key, String(value));
135
+ });
136
+ const url = urlObj.href;
137
+ const isJson = Array.isArray(body) || !!body && Object.getPrototypeOf(body) === Object.prototype;
138
+ const waitTimeout = (time) => new Promise((resolve) => setTimeout(resolve, time));
139
+ let option = {
140
+ ...offset,
141
+ method: method || (body ? "POST" : "GET"),
142
+ headers: { ...isJson && { "Content-Type": "application/json" }, ...headers },
143
+ ...body && { body: isJson ? JSON.stringify(body) : body }
144
+ };
145
+ for (const hook of beforeRequest) {
146
+ const modified = await hook(option);
147
+ if (modified) option = { ...option, ...modified };
148
+ }
149
+ let req;
150
+ for (let i = 0; i < count; i++) {
151
+ const controller = new AbortController();
152
+ const abort = setTimeout(() => controller.abort(), max);
153
+ option.signal = controller.signal;
154
+ try {
155
+ req = await fetch(url, option);
156
+ for (const hook of afterRequest) {
157
+ req = await hook(req) || req;
158
+ }
159
+ if (req.ok) {
160
+ clearTimeout(abort);
161
+ break;
162
+ }
163
+ } catch (error) {
164
+ clearTimeout(abort);
165
+ if (i === count - 1) throw new VigorError(`[vigor] ${url} >> Network Error`, { url, status: 0, message: "Network Error" });
166
+ } finally {
167
+ clearTimeout(abort);
168
+ }
169
+ if (req) {
170
+ const status = req.status;
171
+ if (unretry.has(status)) throw new VigorError(`[vigor] ${url} >> Unretry ${status}`, { url, status, message: "Unretry", data: status });
172
+ const basic = Math.min(Math.pow(backoff, i) * 1e3, wait) + Math.random() * jitter;
173
+ if (status === 429) {
174
+ const rHeader = retryHeader.map((h) => req?.headers.get(h)).find(Boolean);
175
+ const delay = rHeader ? isNaN(Number(rHeader)) ? new Date(rHeader).getTime() - Date.now() : Number(rHeader) * 1e3 : 0;
176
+ const parsedDelay = Math.max(0, delay) + Math.random() * jitter;
177
+ if (parsedDelay > wait) throw new VigorError(`[vigor] ${url} >> Timeouted ${parsedDelay}ms`, { url, status, message: "Timeouted", data: parsedDelay });
178
+ await waitTimeout(parsedDelay || basic);
179
+ } else {
180
+ await waitTimeout(basic);
181
+ }
182
+ }
183
+ }
184
+ if (!req) throw new Error("No response");
185
+ let currentReq = req;
186
+ for (const hook of beforeResponse) {
187
+ currentReq = await hook(currentReq);
188
+ }
189
+ if (!currentReq.ok) throw new VigorError(`[vigor] ${url} >> Failed`, { url, status: currentReq.status, message: "Failed" });
190
+ let res = await (async () => {
191
+ if (original) return currentReq;
192
+ if (parse) {
193
+ const target = currentReq[parse];
194
+ return typeof target === "function" ? await target.call(currentReq) : target;
195
+ }
196
+ const contentType = currentReq.headers.get("Content-Type") || "";
197
+ if (/json/.test(contentType)) return await currentReq.json();
198
+ if (/(image|video|audio|pdf)/.test(contentType)) return await currentReq.blob();
199
+ return await currentReq.text();
200
+ })();
201
+ for (const hook of afterResponse) {
202
+ res = await hook(res);
203
+ }
204
+ return res;
205
+ } catch (error) {
206
+ let currentError = error;
207
+ for (const hook of onError) {
208
+ const result = await hook(currentError);
209
+ if (result !== void 0 && !(result instanceof Error)) return result;
210
+ currentError = result || currentError;
211
+ }
212
+ throw currentError;
213
+ }
214
+ }
215
+ };
216
+ var VigorAll = class _VigorAll {
217
+ constructor(config) {
218
+ this._config = config || { limit: 10, jitter: 1e3, promises: [] };
219
+ }
220
+ _next(changes) {
221
+ return new _VigorAll({ ...this._config, ...changes });
222
+ }
223
+ limit(arg) {
224
+ return this._next({ limit: arg });
225
+ }
226
+ jitter(arg) {
227
+ return this._next({ jitter: arg });
228
+ }
229
+ promises(...args) {
230
+ return this._next({ promises: [...this._config.promises, ...args] });
231
+ }
232
+ async request() {
233
+ const { limit, jitter, promises } = this._config;
234
+ const results = [];
235
+ const executing = /* @__PURE__ */ new Set();
236
+ for (const task of promises) {
237
+ const p = Promise.resolve().then(() => new Promise((res) => setTimeout(res, Math.random() * jitter))).then(() => task());
238
+ results.push(p);
239
+ executing.add(p);
240
+ p.finally(() => executing.delete(p));
241
+ if (executing.size >= limit) await Promise.race(executing);
242
+ }
243
+ const ready = await Promise.allSettled(results);
244
+ return ready.map((i) => {
245
+ if (i.status === "fulfilled") return i.value;
246
+ return i.reason instanceof VigorError ? i.reason : new VigorError(i.reason?.message || "Unknown", { message: i.reason?.message || "Unknown" });
247
+ });
248
+ }
249
+ };
250
+ var Vigor = class {
251
+ fetch(origin, config) {
252
+ return new VigorFetch(origin, config);
253
+ }
254
+ all(config) {
255
+ return new VigorAll(config);
256
+ }
257
+ };
258
+ var index_default = Vigor;
259
+ export {
260
+ index_default as default
261
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vigor-fetch",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Smart, zero-dependency HTTP client with self-healing retries for rate-limited servers.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -9,20 +9,22 @@
9
9
  "dist"
10
10
  ],
11
11
  "scripts": {
12
- "build": "tsc",
13
- "prepublishOnly": "npm run build"
12
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean",
13
+ "dev": "tsup src/index.ts --format cjs,esm --watch --dts"
14
14
  },
15
15
  "keywords": [
16
16
  "fetch",
17
+ "PromiseAll",
17
18
  "retry",
18
19
  "backoff",
19
20
  "ratelimit",
20
21
  "typescript",
21
22
  "zero-dependency"
22
23
  ],
23
- "author": "Your Name",
24
+ "author": "Uav1010",
24
25
  "license": "MIT",
25
26
  "devDependencies": {
27
+ "tsup": "^8.5.1",
26
28
  "typescript": "^5.9.3"
27
29
  },
28
30
  "engines": {