rezo 1.0.44 → 1.0.45

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.
Files changed (37) hide show
  1. package/dist/adapters/entries/curl.d.ts +115 -0
  2. package/dist/adapters/entries/fetch.d.ts +115 -0
  3. package/dist/adapters/entries/http.d.ts +115 -0
  4. package/dist/adapters/entries/http2.d.ts +115 -0
  5. package/dist/adapters/entries/react-native.d.ts +115 -0
  6. package/dist/adapters/entries/xhr.d.ts +115 -0
  7. package/dist/adapters/fetch.cjs +18 -0
  8. package/dist/adapters/fetch.js +18 -0
  9. package/dist/adapters/http.cjs +18 -0
  10. package/dist/adapters/http.js +18 -0
  11. package/dist/adapters/http2.cjs +18 -0
  12. package/dist/adapters/http2.js +18 -0
  13. package/dist/adapters/index.cjs +6 -6
  14. package/dist/adapters/xhr.cjs +19 -0
  15. package/dist/adapters/xhr.js +19 -0
  16. package/dist/cache/index.cjs +9 -9
  17. package/dist/core/hooks.cjs +4 -2
  18. package/dist/core/hooks.js +4 -2
  19. package/dist/crawler/index.cjs +40 -40
  20. package/dist/crawler.d.ts +115 -0
  21. package/dist/entries/crawler.cjs +5 -5
  22. package/dist/index.cjs +27 -27
  23. package/dist/index.d.ts +115 -0
  24. package/dist/internal/agents/index.cjs +10 -10
  25. package/dist/platform/browser.d.ts +115 -0
  26. package/dist/platform/bun.d.ts +115 -0
  27. package/dist/platform/deno.d.ts +115 -0
  28. package/dist/platform/node.d.ts +115 -0
  29. package/dist/platform/react-native.d.ts +115 -0
  30. package/dist/platform/worker.d.ts +115 -0
  31. package/dist/proxy/index.cjs +5 -5
  32. package/dist/proxy/index.js +1 -1
  33. package/dist/queue/index.cjs +8 -8
  34. package/dist/responses/universal/index.cjs +11 -11
  35. package/dist/utils/rate-limit-wait.cjs +217 -0
  36. package/dist/utils/rate-limit-wait.js +208 -0
  37. package/package.json +1 -1
@@ -0,0 +1,217 @@
1
+ const { RezoHeaders } = require('./headers.cjs');
2
+ const DEFAULT_WAIT_STATUS_CODES = exports.DEFAULT_WAIT_STATUS_CODES = [429];
3
+ const DEFAULT_MAX_WAIT_TIME = exports.DEFAULT_MAX_WAIT_TIME = 60000;
4
+ const DEFAULT_WAIT_TIME = exports.DEFAULT_WAIT_TIME = 1000;
5
+ const DEFAULT_MAX_WAIT_ATTEMPTS = exports.DEFAULT_MAX_WAIT_ATTEMPTS = 3;
6
+ function shouldWaitOnStatus(status, waitOnStatus) {
7
+ if (!waitOnStatus)
8
+ return false;
9
+ if (waitOnStatus === true) {
10
+ return DEFAULT_WAIT_STATUS_CODES.includes(status);
11
+ }
12
+ if (Array.isArray(waitOnStatus)) {
13
+ return waitOnStatus.includes(status);
14
+ }
15
+ return false;
16
+ }
17
+ function parseRetryAfterHeader(value) {
18
+ if (!value)
19
+ return null;
20
+ const trimmed = value.trim();
21
+ const seconds = parseInt(trimmed, 10);
22
+ if (!isNaN(seconds) && seconds >= 0) {
23
+ return seconds * 1000;
24
+ }
25
+ const date = new Date(trimmed);
26
+ if (!isNaN(date.getTime())) {
27
+ const delayMs = date.getTime() - Date.now();
28
+ return delayMs > 0 ? delayMs : 0;
29
+ }
30
+ return null;
31
+ }
32
+ function getNestedValue(obj, path) {
33
+ if (!obj || typeof path !== "string")
34
+ return;
35
+ const parts = path.split(".");
36
+ let current = obj;
37
+ for (const part of parts) {
38
+ if (current === null || current === undefined)
39
+ return;
40
+ if (typeof current !== "object")
41
+ return;
42
+ current = current[part];
43
+ }
44
+ return current;
45
+ }
46
+ function extractWaitTime(response, options) {
47
+ const source = options.waitTimeSource ?? "retry-after";
48
+ const defaultWaitTime = options.defaultWaitTime ?? DEFAULT_WAIT_TIME;
49
+ if (typeof source === "function") {
50
+ try {
51
+ const seconds = source(response);
52
+ if (seconds !== null && seconds !== undefined && seconds > 0) {
53
+ return {
54
+ waitTimeMs: seconds * 1000,
55
+ source: "function"
56
+ };
57
+ }
58
+ } catch {}
59
+ return {
60
+ waitTimeMs: defaultWaitTime,
61
+ source: "default"
62
+ };
63
+ }
64
+ if (source === "retry-after") {
65
+ const headerValue = response.headers?.get?.("retry-after") ?? response.headers?.get?.("Retry-After") ?? response.headers?.["retry-after"] ?? response.headers?.["Retry-After"];
66
+ const waitTimeMs = parseRetryAfterHeader(headerValue);
67
+ if (waitTimeMs !== null) {
68
+ return {
69
+ waitTimeMs,
70
+ source: "header",
71
+ sourcePath: "retry-after"
72
+ };
73
+ }
74
+ return {
75
+ waitTimeMs: defaultWaitTime,
76
+ source: "default"
77
+ };
78
+ }
79
+ if (typeof source === "object" && "header" in source) {
80
+ const headerName = source.header;
81
+ const headerValue = response.headers?.get?.(headerName) ?? response.headers?.get?.(headerName.toLowerCase()) ?? response.headers?.[headerName] ?? response.headers?.[headerName.toLowerCase()];
82
+ if (headerValue) {
83
+ const numValue = parseInt(headerValue, 10);
84
+ if (!isNaN(numValue)) {
85
+ const now = Math.floor(Date.now() / 1000);
86
+ if (numValue > now && numValue < now + 86400 * 365) {
87
+ const waitTimeMs = (numValue - now) * 1000;
88
+ return {
89
+ waitTimeMs: waitTimeMs > 0 ? waitTimeMs : defaultWaitTime,
90
+ source: "header",
91
+ sourcePath: headerName
92
+ };
93
+ }
94
+ return {
95
+ waitTimeMs: numValue * 1000,
96
+ source: "header",
97
+ sourcePath: headerName
98
+ };
99
+ }
100
+ const waitTimeMs = parseRetryAfterHeader(headerValue);
101
+ if (waitTimeMs !== null) {
102
+ return {
103
+ waitTimeMs,
104
+ source: "header",
105
+ sourcePath: headerName
106
+ };
107
+ }
108
+ }
109
+ return {
110
+ waitTimeMs: defaultWaitTime,
111
+ source: "default"
112
+ };
113
+ }
114
+ if (typeof source === "object" && "body" in source) {
115
+ const bodyPath = source.body;
116
+ const value = getNestedValue(response.data, bodyPath);
117
+ if (value !== null && value !== undefined) {
118
+ const numValue = typeof value === "number" ? value : parseInt(String(value), 10);
119
+ if (!isNaN(numValue) && numValue > 0) {
120
+ return {
121
+ waitTimeMs: numValue * 1000,
122
+ source: "body",
123
+ sourcePath: bodyPath
124
+ };
125
+ }
126
+ }
127
+ return {
128
+ waitTimeMs: defaultWaitTime,
129
+ source: "default"
130
+ };
131
+ }
132
+ return {
133
+ waitTimeMs: defaultWaitTime,
134
+ source: "default"
135
+ };
136
+ }
137
+ function createRateLimitWaitEvent(status, waitTimeMs, attempt, maxAttempts, source, sourcePath, url, method) {
138
+ return {
139
+ status,
140
+ waitTime: waitTimeMs,
141
+ attempt,
142
+ maxAttempts,
143
+ source,
144
+ sourcePath,
145
+ url,
146
+ method,
147
+ timestamp: Date.now()
148
+ };
149
+ }
150
+ async function executeRateLimitWaitHooks(event, config) {
151
+ const hooks = config.hooks?.onRateLimitWait;
152
+ if (!hooks || hooks.length === 0)
153
+ return;
154
+ for (const hook of hooks) {
155
+ try {
156
+ await hook(event, config);
157
+ } catch (err) {
158
+ if (config.debug) {
159
+ console.log("[Rezo Debug] onRateLimitWait hook error:", err);
160
+ }
161
+ }
162
+ }
163
+ }
164
+ function sleep(ms) {
165
+ return new Promise((resolve) => setTimeout(resolve, ms));
166
+ }
167
+ function normalizeHeaders(headers) {
168
+ if (!headers)
169
+ return new RezoHeaders;
170
+ if (headers instanceof RezoHeaders)
171
+ return headers;
172
+ if (typeof headers.get === "function")
173
+ return headers;
174
+ try {
175
+ return new RezoHeaders(headers);
176
+ } catch {
177
+ return new RezoHeaders;
178
+ }
179
+ }
180
+ async function handleRateLimitWait(ctx) {
181
+ const { status, headers, data, url, method, config, options, currentWaitAttempt } = ctx;
182
+ if (!shouldWaitOnStatus(status, options.waitOnStatus)) {
183
+ return { shouldRetry: false, waitAttempt: currentWaitAttempt, waitedMs: 0 };
184
+ }
185
+ const maxAttempts = options.maxWaitAttempts ?? DEFAULT_MAX_WAIT_ATTEMPTS;
186
+ const maxWaitTime = options.maxWaitTime ?? DEFAULT_MAX_WAIT_TIME;
187
+ const nextAttempt = currentWaitAttempt + 1;
188
+ if (nextAttempt > maxAttempts) {
189
+ return { shouldRetry: false, waitAttempt: currentWaitAttempt, waitedMs: 0 };
190
+ }
191
+ const normalizedHeaders = normalizeHeaders(headers);
192
+ const extracted = extractWaitTime({ status, headers: normalizedHeaders, data }, options);
193
+ let waitTimeMs = extracted.waitTimeMs;
194
+ if (maxWaitTime > 0 && waitTimeMs > maxWaitTime) {
195
+ return { shouldRetry: false, waitAttempt: currentWaitAttempt, waitedMs: 0 };
196
+ }
197
+ const event = createRateLimitWaitEvent(status, waitTimeMs, nextAttempt, maxAttempts, extracted.source, extracted.sourcePath, url, method);
198
+ await executeRateLimitWaitHooks(event, config);
199
+ if (config.debug) {
200
+ console.log(`[Rezo Debug] Rate limit (${status}) - waiting ${waitTimeMs}ms (attempt ${nextAttempt}/${maxAttempts}, source: ${extracted.source}${extracted.sourcePath ? `:${extracted.sourcePath}` : ""})`);
201
+ }
202
+ await sleep(waitTimeMs);
203
+ return {
204
+ shouldRetry: true,
205
+ waitAttempt: nextAttempt,
206
+ waitedMs: waitTimeMs
207
+ };
208
+ }
209
+
210
+ exports.shouldWaitOnStatus = shouldWaitOnStatus;
211
+ exports.parseRetryAfterHeader = parseRetryAfterHeader;
212
+ exports.getNestedValue = getNestedValue;
213
+ exports.extractWaitTime = extractWaitTime;
214
+ exports.createRateLimitWaitEvent = createRateLimitWaitEvent;
215
+ exports.executeRateLimitWaitHooks = executeRateLimitWaitHooks;
216
+ exports.sleep = sleep;
217
+ exports.handleRateLimitWait = handleRateLimitWait;
@@ -0,0 +1,208 @@
1
+ import { RezoHeaders } from './headers.js';
2
+ export const DEFAULT_WAIT_STATUS_CODES = [429];
3
+ export const DEFAULT_MAX_WAIT_TIME = 60000;
4
+ export const DEFAULT_WAIT_TIME = 1000;
5
+ export const DEFAULT_MAX_WAIT_ATTEMPTS = 3;
6
+ export function shouldWaitOnStatus(status, waitOnStatus) {
7
+ if (!waitOnStatus)
8
+ return false;
9
+ if (waitOnStatus === true) {
10
+ return DEFAULT_WAIT_STATUS_CODES.includes(status);
11
+ }
12
+ if (Array.isArray(waitOnStatus)) {
13
+ return waitOnStatus.includes(status);
14
+ }
15
+ return false;
16
+ }
17
+ export function parseRetryAfterHeader(value) {
18
+ if (!value)
19
+ return null;
20
+ const trimmed = value.trim();
21
+ const seconds = parseInt(trimmed, 10);
22
+ if (!isNaN(seconds) && seconds >= 0) {
23
+ return seconds * 1000;
24
+ }
25
+ const date = new Date(trimmed);
26
+ if (!isNaN(date.getTime())) {
27
+ const delayMs = date.getTime() - Date.now();
28
+ return delayMs > 0 ? delayMs : 0;
29
+ }
30
+ return null;
31
+ }
32
+ export function getNestedValue(obj, path) {
33
+ if (!obj || typeof path !== "string")
34
+ return;
35
+ const parts = path.split(".");
36
+ let current = obj;
37
+ for (const part of parts) {
38
+ if (current === null || current === undefined)
39
+ return;
40
+ if (typeof current !== "object")
41
+ return;
42
+ current = current[part];
43
+ }
44
+ return current;
45
+ }
46
+ export function extractWaitTime(response, options) {
47
+ const source = options.waitTimeSource ?? "retry-after";
48
+ const defaultWaitTime = options.defaultWaitTime ?? DEFAULT_WAIT_TIME;
49
+ if (typeof source === "function") {
50
+ try {
51
+ const seconds = source(response);
52
+ if (seconds !== null && seconds !== undefined && seconds > 0) {
53
+ return {
54
+ waitTimeMs: seconds * 1000,
55
+ source: "function"
56
+ };
57
+ }
58
+ } catch {}
59
+ return {
60
+ waitTimeMs: defaultWaitTime,
61
+ source: "default"
62
+ };
63
+ }
64
+ if (source === "retry-after") {
65
+ const headerValue = response.headers?.get?.("retry-after") ?? response.headers?.get?.("Retry-After") ?? response.headers?.["retry-after"] ?? response.headers?.["Retry-After"];
66
+ const waitTimeMs = parseRetryAfterHeader(headerValue);
67
+ if (waitTimeMs !== null) {
68
+ return {
69
+ waitTimeMs,
70
+ source: "header",
71
+ sourcePath: "retry-after"
72
+ };
73
+ }
74
+ return {
75
+ waitTimeMs: defaultWaitTime,
76
+ source: "default"
77
+ };
78
+ }
79
+ if (typeof source === "object" && "header" in source) {
80
+ const headerName = source.header;
81
+ const headerValue = response.headers?.get?.(headerName) ?? response.headers?.get?.(headerName.toLowerCase()) ?? response.headers?.[headerName] ?? response.headers?.[headerName.toLowerCase()];
82
+ if (headerValue) {
83
+ const numValue = parseInt(headerValue, 10);
84
+ if (!isNaN(numValue)) {
85
+ const now = Math.floor(Date.now() / 1000);
86
+ if (numValue > now && numValue < now + 86400 * 365) {
87
+ const waitTimeMs = (numValue - now) * 1000;
88
+ return {
89
+ waitTimeMs: waitTimeMs > 0 ? waitTimeMs : defaultWaitTime,
90
+ source: "header",
91
+ sourcePath: headerName
92
+ };
93
+ }
94
+ return {
95
+ waitTimeMs: numValue * 1000,
96
+ source: "header",
97
+ sourcePath: headerName
98
+ };
99
+ }
100
+ const waitTimeMs = parseRetryAfterHeader(headerValue);
101
+ if (waitTimeMs !== null) {
102
+ return {
103
+ waitTimeMs,
104
+ source: "header",
105
+ sourcePath: headerName
106
+ };
107
+ }
108
+ }
109
+ return {
110
+ waitTimeMs: defaultWaitTime,
111
+ source: "default"
112
+ };
113
+ }
114
+ if (typeof source === "object" && "body" in source) {
115
+ const bodyPath = source.body;
116
+ const value = getNestedValue(response.data, bodyPath);
117
+ if (value !== null && value !== undefined) {
118
+ const numValue = typeof value === "number" ? value : parseInt(String(value), 10);
119
+ if (!isNaN(numValue) && numValue > 0) {
120
+ return {
121
+ waitTimeMs: numValue * 1000,
122
+ source: "body",
123
+ sourcePath: bodyPath
124
+ };
125
+ }
126
+ }
127
+ return {
128
+ waitTimeMs: defaultWaitTime,
129
+ source: "default"
130
+ };
131
+ }
132
+ return {
133
+ waitTimeMs: defaultWaitTime,
134
+ source: "default"
135
+ };
136
+ }
137
+ export function createRateLimitWaitEvent(status, waitTimeMs, attempt, maxAttempts, source, sourcePath, url, method) {
138
+ return {
139
+ status,
140
+ waitTime: waitTimeMs,
141
+ attempt,
142
+ maxAttempts,
143
+ source,
144
+ sourcePath,
145
+ url,
146
+ method,
147
+ timestamp: Date.now()
148
+ };
149
+ }
150
+ export async function executeRateLimitWaitHooks(event, config) {
151
+ const hooks = config.hooks?.onRateLimitWait;
152
+ if (!hooks || hooks.length === 0)
153
+ return;
154
+ for (const hook of hooks) {
155
+ try {
156
+ await hook(event, config);
157
+ } catch (err) {
158
+ if (config.debug) {
159
+ console.log("[Rezo Debug] onRateLimitWait hook error:", err);
160
+ }
161
+ }
162
+ }
163
+ }
164
+ export function sleep(ms) {
165
+ return new Promise((resolve) => setTimeout(resolve, ms));
166
+ }
167
+ function normalizeHeaders(headers) {
168
+ if (!headers)
169
+ return new RezoHeaders;
170
+ if (headers instanceof RezoHeaders)
171
+ return headers;
172
+ if (typeof headers.get === "function")
173
+ return headers;
174
+ try {
175
+ return new RezoHeaders(headers);
176
+ } catch {
177
+ return new RezoHeaders;
178
+ }
179
+ }
180
+ export async function handleRateLimitWait(ctx) {
181
+ const { status, headers, data, url, method, config, options, currentWaitAttempt } = ctx;
182
+ if (!shouldWaitOnStatus(status, options.waitOnStatus)) {
183
+ return { shouldRetry: false, waitAttempt: currentWaitAttempt, waitedMs: 0 };
184
+ }
185
+ const maxAttempts = options.maxWaitAttempts ?? DEFAULT_MAX_WAIT_ATTEMPTS;
186
+ const maxWaitTime = options.maxWaitTime ?? DEFAULT_MAX_WAIT_TIME;
187
+ const nextAttempt = currentWaitAttempt + 1;
188
+ if (nextAttempt > maxAttempts) {
189
+ return { shouldRetry: false, waitAttempt: currentWaitAttempt, waitedMs: 0 };
190
+ }
191
+ const normalizedHeaders = normalizeHeaders(headers);
192
+ const extracted = extractWaitTime({ status, headers: normalizedHeaders, data }, options);
193
+ let waitTimeMs = extracted.waitTimeMs;
194
+ if (maxWaitTime > 0 && waitTimeMs > maxWaitTime) {
195
+ return { shouldRetry: false, waitAttempt: currentWaitAttempt, waitedMs: 0 };
196
+ }
197
+ const event = createRateLimitWaitEvent(status, waitTimeMs, nextAttempt, maxAttempts, extracted.source, extracted.sourcePath, url, method);
198
+ await executeRateLimitWaitHooks(event, config);
199
+ if (config.debug) {
200
+ console.log(`[Rezo Debug] Rate limit (${status}) - waiting ${waitTimeMs}ms (attempt ${nextAttempt}/${maxAttempts}, source: ${extracted.source}${extracted.sourcePath ? `:${extracted.sourcePath}` : ""})`);
201
+ }
202
+ await sleep(waitTimeMs);
203
+ return {
204
+ shouldRetry: true,
205
+ waitAttempt: nextAttempt,
206
+ waitedMs: waitTimeMs
207
+ };
208
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rezo",
3
- "version": "1.0.44",
3
+ "version": "1.0.45",
4
4
  "description": "Lightning-fast, enterprise-grade HTTP client for modern JavaScript. Full HTTP/2 support, intelligent cookie management, multiple adapters (HTTP, Fetch, cURL, XHR), streaming, proxy support (HTTP/HTTPS/SOCKS), and cross-environment compatibility.",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",