rezo 1.0.4 → 1.0.6

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 (47) hide show
  1. package/README.md +352 -9
  2. package/dist/adapters/curl.cjs +796 -0
  3. package/dist/adapters/curl.js +796 -0
  4. package/dist/adapters/entries/curl.d.ts +2332 -20
  5. package/dist/adapters/entries/fetch.d.ts +289 -20
  6. package/dist/adapters/entries/http.d.ts +289 -20
  7. package/dist/adapters/entries/http2.d.ts +289 -20
  8. package/dist/adapters/entries/react-native.d.ts +289 -20
  9. package/dist/adapters/entries/xhr.d.ts +289 -20
  10. package/dist/adapters/index.cjs +6 -6
  11. package/dist/adapters/picker.cjs +2 -2
  12. package/dist/adapters/picker.js +2 -2
  13. package/dist/cache/index.cjs +13 -13
  14. package/dist/core/rezo.cjs +2 -2
  15. package/dist/core/rezo.js +2 -2
  16. package/dist/crawler.d.ts +291 -22
  17. package/dist/entries/crawler.cjs +5 -5
  18. package/dist/index.cjs +23 -18
  19. package/dist/index.d.ts +556 -20
  20. package/dist/index.js +1 -0
  21. package/dist/platform/browser.d.ts +289 -20
  22. package/dist/platform/bun.d.ts +289 -20
  23. package/dist/platform/deno.d.ts +289 -20
  24. package/dist/platform/node.d.ts +289 -20
  25. package/dist/platform/react-native.d.ts +289 -20
  26. package/dist/platform/worker.d.ts +289 -20
  27. package/dist/plugin/crawler-options.cjs +1 -1
  28. package/dist/plugin/crawler-options.js +1 -1
  29. package/dist/plugin/crawler.cjs +2 -2
  30. package/dist/plugin/crawler.js +2 -2
  31. package/dist/plugin/index.cjs +36 -36
  32. package/dist/proxy/index.cjs +2 -2
  33. package/dist/proxy/manager.cjs +14 -1
  34. package/dist/proxy/manager.js +14 -1
  35. package/dist/queue/http-queue.cjs +313 -0
  36. package/dist/queue/http-queue.js +312 -0
  37. package/dist/queue/index.cjs +8 -0
  38. package/dist/queue/index.js +6 -0
  39. package/dist/queue/queue.cjs +346 -0
  40. package/dist/queue/queue.js +344 -0
  41. package/dist/queue/types.cjs +17 -0
  42. package/dist/queue/types.js +17 -0
  43. package/dist/types/curl-options.cjs +25 -0
  44. package/dist/types/curl-options.js +25 -0
  45. package/dist/utils/http-config.cjs +0 -15
  46. package/dist/utils/http-config.js +0 -15
  47. package/package.json +1 -2
@@ -1,3 +1,4 @@
1
+ import { parseProxyString } from './index.js';
1
2
  function generateProxyId() {
2
3
  return `proxy_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
3
4
  }
@@ -23,13 +24,25 @@ export class ProxyManager {
23
24
  afterProxyEnable: []
24
25
  };
25
26
  constructor(config) {
27
+ const parsedProxies = [];
28
+ for (const proxy of config.proxies) {
29
+ if (typeof proxy === "string") {
30
+ const parsed = parseProxyString(proxy);
31
+ if (parsed) {
32
+ parsedProxies.push(parsed);
33
+ }
34
+ } else {
35
+ parsedProxies.push(proxy);
36
+ }
37
+ }
26
38
  this.config = {
27
39
  failWithoutProxy: true,
28
40
  autoDisableDeadProxies: false,
29
41
  maxFailures: 3,
30
42
  retryWithNextProxy: false,
31
43
  maxProxyRetries: 3,
32
- ...config
44
+ ...config,
45
+ proxies: parsedProxies
33
46
  };
34
47
  for (const proxy of this.config.proxies) {
35
48
  if (!proxy.id) {
@@ -0,0 +1,313 @@
1
+ const { RezoQueue } = require('./queue.cjs');
2
+ const { HttpMethodPriority } = require('./types.cjs');
3
+ function extractDomain(url) {
4
+ try {
5
+ const parsed = new URL(url);
6
+ return parsed.hostname;
7
+ } catch {
8
+ const match = url.match(/^(?:https?:\/\/)?([^/:]+)/i);
9
+ return match?.[1] ?? "unknown";
10
+ }
11
+ }
12
+ function generateId() {
13
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 9)}`;
14
+ }
15
+
16
+ class HttpQueue extends RezoQueue {
17
+ domainQueues = new Map;
18
+ domainPending = new Map;
19
+ domainPaused = new Map;
20
+ domainRateLimited = new Map;
21
+ domainConcurrencyLimits = new Map;
22
+ httpStatsData;
23
+ httpEventHandlers = new Map;
24
+ httpConfig;
25
+ constructor(config = {}) {
26
+ super(config);
27
+ this.httpConfig = {
28
+ ...this.config,
29
+ domainConcurrency: config.domainConcurrency ?? 1 / 0,
30
+ requestsPerSecond: config.requestsPerSecond ?? 0,
31
+ respectRetryAfter: config.respectRetryAfter ?? true,
32
+ respectRateLimitHeaders: config.respectRateLimitHeaders ?? true,
33
+ autoRetry: config.autoRetry ?? false,
34
+ maxRetries: config.maxRetries ?? 3,
35
+ retryDelay: config.retryDelay ?? 1000,
36
+ retryStatusCodes: config.retryStatusCodes ?? [429, 500, 502, 503, 504]
37
+ };
38
+ if (typeof config.domainConcurrency === "object") {
39
+ for (const [domain, limit] of Object.entries(config.domainConcurrency)) {
40
+ this.domainConcurrencyLimits.set(domain, limit);
41
+ }
42
+ }
43
+ this.httpStatsData = {
44
+ ...this.stats,
45
+ byDomain: {},
46
+ retries: 0,
47
+ rateLimitHits: 0
48
+ };
49
+ if (this.httpConfig.requestsPerSecond > 0) {
50
+ this.config.interval = 1000;
51
+ this.config.intervalCap = this.httpConfig.requestsPerSecond;
52
+ }
53
+ }
54
+ start() {
55
+ super.start();
56
+ this.tryRunHttpNext();
57
+ }
58
+ get httpStats() {
59
+ return {
60
+ ...this.stats,
61
+ byDomain: { ...this.httpStatsData.byDomain },
62
+ retries: this.httpStatsData.retries,
63
+ rateLimitHits: this.httpStatsData.rateLimitHits
64
+ };
65
+ }
66
+ addHttp(fn, options = {}) {
67
+ return new Promise((resolve, reject) => {
68
+ const domain = options.domain ?? "default";
69
+ const method = (options.method ?? "GET").toUpperCase();
70
+ const effectivePriority = options.priority ?? (HttpMethodPriority[method] ?? HttpMethodPriority.GET);
71
+ const maxRetries = typeof options.retry === "number" ? options.retry : options.retry === true ? this.httpConfig.maxRetries : 0;
72
+ const task = {
73
+ id: options.id ?? generateId(),
74
+ fn,
75
+ priority: effectivePriority,
76
+ timeout: options.timeout ?? this.config.timeout,
77
+ signal: options.signal,
78
+ resolve,
79
+ reject,
80
+ addedAt: Date.now(),
81
+ domain,
82
+ method,
83
+ retryCount: 0,
84
+ maxRetries: this.httpConfig.autoRetry ? Math.max(maxRetries, this.httpConfig.maxRetries) : maxRetries,
85
+ retryDelay: options.retryDelay
86
+ };
87
+ if (options.signal?.aborted) {
88
+ reject(new Error("Task was cancelled before starting"));
89
+ return;
90
+ }
91
+ options.signal?.addEventListener("abort", () => {
92
+ this.cancelHttp(task.id);
93
+ });
94
+ this.ensureDomainStats(domain);
95
+ this.insertHttpTask(task);
96
+ this.emitHttp("add", { id: task.id, priority: task.priority });
97
+ if (!this.isPaused) {
98
+ this.tryRunHttpNext();
99
+ }
100
+ });
101
+ }
102
+ pauseDomain(domain) {
103
+ this.domainPaused.set(domain, true);
104
+ }
105
+ resumeDomain(domain) {
106
+ this.domainPaused.delete(domain);
107
+ this.emitHttp("domainAvailable", { domain });
108
+ this.tryRunHttpNext();
109
+ }
110
+ setDomainConcurrency(domain, limit) {
111
+ this.domainConcurrencyLimits.set(domain, limit);
112
+ this.tryRunHttpNext();
113
+ }
114
+ getDomainState(domain) {
115
+ return {
116
+ pending: this.domainPending.get(domain) ?? 0,
117
+ size: this.domainQueues.get(domain)?.length ?? 0,
118
+ isPaused: this.domainPaused.get(domain) ?? false,
119
+ rateLimitedUntil: this.domainRateLimited.get(domain)
120
+ };
121
+ }
122
+ handleRateLimit(domain, retryAfter) {
123
+ const until = Date.now() + retryAfter * 1000;
124
+ this.domainRateLimited.set(domain, until);
125
+ this.httpStatsData.rateLimitHits++;
126
+ this.ensureDomainStats(domain);
127
+ this.httpStatsData.byDomain[domain].rateLimited++;
128
+ this.emitHttp("rateLimited", { domain, retryAfter });
129
+ setTimeout(() => {
130
+ this.domainRateLimited.delete(domain);
131
+ this.emitHttp("domainAvailable", { domain });
132
+ this.tryRunHttpNext();
133
+ }, retryAfter * 1000);
134
+ }
135
+ cancelHttp(id) {
136
+ for (const [domain, queue] of this.domainQueues.entries()) {
137
+ const index = queue.findIndex((t) => t.id === id);
138
+ if (index !== -1) {
139
+ const [task] = queue.splice(index, 1);
140
+ task.reject(new Error("Task was cancelled"));
141
+ this.ensureDomainStats(domain);
142
+ this.httpStatsData.byDomain[domain].failed++;
143
+ this.emitHttp("cancelled", { id });
144
+ return true;
145
+ }
146
+ }
147
+ return false;
148
+ }
149
+ onHttp(event, handler) {
150
+ if (!this.httpEventHandlers.has(event)) {
151
+ this.httpEventHandlers.set(event, new Set);
152
+ }
153
+ this.httpEventHandlers.get(event).add(handler);
154
+ }
155
+ offHttp(event, handler) {
156
+ this.httpEventHandlers.get(event)?.delete(handler);
157
+ }
158
+ clearHttp() {
159
+ for (const [domain, queue] of this.domainQueues.entries()) {
160
+ this.ensureDomainStats(domain);
161
+ for (const task of queue) {
162
+ task.reject(new Error("Queue was cleared"));
163
+ this.httpStatsData.byDomain[domain].failed++;
164
+ this.emitHttp("cancelled", { id: task.id });
165
+ }
166
+ }
167
+ this.domainQueues.clear();
168
+ this.domainPending.clear();
169
+ }
170
+ destroy() {
171
+ this.clearHttp();
172
+ this.httpEventHandlers.clear();
173
+ super.destroy();
174
+ }
175
+ insertHttpTask(task) {
176
+ const domain = task.domain ?? "default";
177
+ if (!this.domainQueues.has(domain)) {
178
+ this.domainQueues.set(domain, []);
179
+ }
180
+ const queue = this.domainQueues.get(domain);
181
+ let insertIndex = queue.length;
182
+ for (let i = 0;i < queue.length; i++) {
183
+ if (task.priority > queue[i].priority) {
184
+ insertIndex = i;
185
+ break;
186
+ }
187
+ }
188
+ queue.splice(insertIndex, 0, task);
189
+ }
190
+ getDomainLimit(domain) {
191
+ const specificLimit = this.domainConcurrencyLimits.get(domain);
192
+ if (specificLimit !== undefined)
193
+ return specificLimit;
194
+ if (typeof this.httpConfig.domainConcurrency === "number") {
195
+ return this.httpConfig.domainConcurrency;
196
+ }
197
+ return 1 / 0;
198
+ }
199
+ canRunDomain(domain) {
200
+ if (this.domainPaused.get(domain))
201
+ return false;
202
+ const rateLimitedUntil = this.domainRateLimited.get(domain);
203
+ if (rateLimitedUntil && Date.now() < rateLimitedUntil)
204
+ return false;
205
+ const pending = this.domainPending.get(domain) ?? 0;
206
+ const limit = this.getDomainLimit(domain);
207
+ return pending < limit;
208
+ }
209
+ tryRunHttpNext() {
210
+ if (this.isPaused)
211
+ return;
212
+ for (const [domain, queue] of this.domainQueues.entries()) {
213
+ if (queue.length === 0)
214
+ continue;
215
+ if (!this.canRunDomain(domain))
216
+ continue;
217
+ const task = queue.shift();
218
+ this.runHttpTask(task);
219
+ }
220
+ }
221
+ async runHttpTask(task) {
222
+ const domain = task.domain ?? "default";
223
+ const pending = (this.domainPending.get(domain) ?? 0) + 1;
224
+ this.domainPending.set(domain, pending);
225
+ this.ensureDomainStats(domain);
226
+ this.httpStatsData.byDomain[domain].pending++;
227
+ this.emitHttp("start", { id: task.id });
228
+ const startTime = Date.now();
229
+ let timeoutId;
230
+ try {
231
+ let result;
232
+ if (task.timeout && task.timeout > 0) {
233
+ result = await Promise.race([
234
+ task.fn(),
235
+ new Promise((_, reject) => {
236
+ timeoutId = setTimeout(() => {
237
+ this.emitHttp("timeout", { id: task.id });
238
+ reject(new Error(`Task ${task.id} timed out after ${task.timeout}ms`));
239
+ }, task.timeout);
240
+ })
241
+ ]);
242
+ } else {
243
+ result = await task.fn();
244
+ }
245
+ if (timeoutId)
246
+ clearTimeout(timeoutId);
247
+ const duration = Date.now() - startTime;
248
+ this.httpStatsData.byDomain[domain].completed++;
249
+ this.emitHttp("completed", { id: task.id, result, duration });
250
+ task.resolve(result);
251
+ } catch (error) {
252
+ if (timeoutId)
253
+ clearTimeout(timeoutId);
254
+ const shouldRetry = task.retryCount < task.maxRetries;
255
+ if (shouldRetry) {
256
+ task.retryCount++;
257
+ this.httpStatsData.retries++;
258
+ const delay = this.getRetryDelay(task);
259
+ this.emitHttp("retry", {
260
+ id: task.id,
261
+ attempt: task.retryCount,
262
+ error
263
+ });
264
+ setTimeout(() => {
265
+ this.insertHttpTask(task);
266
+ this.tryRunHttpNext();
267
+ }, delay);
268
+ } else {
269
+ this.httpStatsData.byDomain[domain].failed++;
270
+ this.emitHttp("error", { id: task.id, error });
271
+ task.reject(error);
272
+ }
273
+ } finally {
274
+ const newPending = (this.domainPending.get(domain) ?? 1) - 1;
275
+ this.domainPending.set(domain, newPending);
276
+ this.httpStatsData.byDomain[domain].pending = newPending;
277
+ this.tryRunHttpNext();
278
+ }
279
+ }
280
+ getRetryDelay(task) {
281
+ if (task.retryDelay !== undefined) {
282
+ return task.retryDelay;
283
+ }
284
+ const baseDelay = typeof this.httpConfig.retryDelay === "function" ? this.httpConfig.retryDelay(task.retryCount) : this.httpConfig.retryDelay;
285
+ return baseDelay * Math.pow(2, task.retryCount - 1);
286
+ }
287
+ ensureDomainStats(domain) {
288
+ if (!this.httpStatsData.byDomain[domain]) {
289
+ this.httpStatsData.byDomain[domain] = {
290
+ pending: 0,
291
+ completed: 0,
292
+ failed: 0,
293
+ rateLimited: 0
294
+ };
295
+ }
296
+ }
297
+ emitHttp(event, data) {
298
+ const handlers = this.httpEventHandlers.get(event);
299
+ if (handlers) {
300
+ for (const handler of handlers) {
301
+ try {
302
+ handler(data);
303
+ } catch {}
304
+ }
305
+ }
306
+ if (event in this.config) {
307
+ this.emit(event, data);
308
+ }
309
+ }
310
+ }
311
+
312
+ exports.extractDomain = extractDomain;
313
+ exports.HttpQueue = HttpQueue;
@@ -0,0 +1,312 @@
1
+ import { RezoQueue } from './queue.js';
2
+ import { HttpMethodPriority } from './types.js';
3
+ function extractDomain(url) {
4
+ try {
5
+ const parsed = new URL(url);
6
+ return parsed.hostname;
7
+ } catch {
8
+ const match = url.match(/^(?:https?:\/\/)?([^/:]+)/i);
9
+ return match?.[1] ?? "unknown";
10
+ }
11
+ }
12
+ function generateId() {
13
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 9)}`;
14
+ }
15
+
16
+ export class HttpQueue extends RezoQueue {
17
+ domainQueues = new Map;
18
+ domainPending = new Map;
19
+ domainPaused = new Map;
20
+ domainRateLimited = new Map;
21
+ domainConcurrencyLimits = new Map;
22
+ httpStatsData;
23
+ httpEventHandlers = new Map;
24
+ httpConfig;
25
+ constructor(config = {}) {
26
+ super(config);
27
+ this.httpConfig = {
28
+ ...this.config,
29
+ domainConcurrency: config.domainConcurrency ?? 1 / 0,
30
+ requestsPerSecond: config.requestsPerSecond ?? 0,
31
+ respectRetryAfter: config.respectRetryAfter ?? true,
32
+ respectRateLimitHeaders: config.respectRateLimitHeaders ?? true,
33
+ autoRetry: config.autoRetry ?? false,
34
+ maxRetries: config.maxRetries ?? 3,
35
+ retryDelay: config.retryDelay ?? 1000,
36
+ retryStatusCodes: config.retryStatusCodes ?? [429, 500, 502, 503, 504]
37
+ };
38
+ if (typeof config.domainConcurrency === "object") {
39
+ for (const [domain, limit] of Object.entries(config.domainConcurrency)) {
40
+ this.domainConcurrencyLimits.set(domain, limit);
41
+ }
42
+ }
43
+ this.httpStatsData = {
44
+ ...this.stats,
45
+ byDomain: {},
46
+ retries: 0,
47
+ rateLimitHits: 0
48
+ };
49
+ if (this.httpConfig.requestsPerSecond > 0) {
50
+ this.config.interval = 1000;
51
+ this.config.intervalCap = this.httpConfig.requestsPerSecond;
52
+ }
53
+ }
54
+ start() {
55
+ super.start();
56
+ this.tryRunHttpNext();
57
+ }
58
+ get httpStats() {
59
+ return {
60
+ ...this.stats,
61
+ byDomain: { ...this.httpStatsData.byDomain },
62
+ retries: this.httpStatsData.retries,
63
+ rateLimitHits: this.httpStatsData.rateLimitHits
64
+ };
65
+ }
66
+ addHttp(fn, options = {}) {
67
+ return new Promise((resolve, reject) => {
68
+ const domain = options.domain ?? "default";
69
+ const method = (options.method ?? "GET").toUpperCase();
70
+ const effectivePriority = options.priority ?? (HttpMethodPriority[method] ?? HttpMethodPriority.GET);
71
+ const maxRetries = typeof options.retry === "number" ? options.retry : options.retry === true ? this.httpConfig.maxRetries : 0;
72
+ const task = {
73
+ id: options.id ?? generateId(),
74
+ fn,
75
+ priority: effectivePriority,
76
+ timeout: options.timeout ?? this.config.timeout,
77
+ signal: options.signal,
78
+ resolve,
79
+ reject,
80
+ addedAt: Date.now(),
81
+ domain,
82
+ method,
83
+ retryCount: 0,
84
+ maxRetries: this.httpConfig.autoRetry ? Math.max(maxRetries, this.httpConfig.maxRetries) : maxRetries,
85
+ retryDelay: options.retryDelay
86
+ };
87
+ if (options.signal?.aborted) {
88
+ reject(new Error("Task was cancelled before starting"));
89
+ return;
90
+ }
91
+ options.signal?.addEventListener("abort", () => {
92
+ this.cancelHttp(task.id);
93
+ });
94
+ this.ensureDomainStats(domain);
95
+ this.insertHttpTask(task);
96
+ this.emitHttp("add", { id: task.id, priority: task.priority });
97
+ if (!this.isPaused) {
98
+ this.tryRunHttpNext();
99
+ }
100
+ });
101
+ }
102
+ pauseDomain(domain) {
103
+ this.domainPaused.set(domain, true);
104
+ }
105
+ resumeDomain(domain) {
106
+ this.domainPaused.delete(domain);
107
+ this.emitHttp("domainAvailable", { domain });
108
+ this.tryRunHttpNext();
109
+ }
110
+ setDomainConcurrency(domain, limit) {
111
+ this.domainConcurrencyLimits.set(domain, limit);
112
+ this.tryRunHttpNext();
113
+ }
114
+ getDomainState(domain) {
115
+ return {
116
+ pending: this.domainPending.get(domain) ?? 0,
117
+ size: this.domainQueues.get(domain)?.length ?? 0,
118
+ isPaused: this.domainPaused.get(domain) ?? false,
119
+ rateLimitedUntil: this.domainRateLimited.get(domain)
120
+ };
121
+ }
122
+ handleRateLimit(domain, retryAfter) {
123
+ const until = Date.now() + retryAfter * 1000;
124
+ this.domainRateLimited.set(domain, until);
125
+ this.httpStatsData.rateLimitHits++;
126
+ this.ensureDomainStats(domain);
127
+ this.httpStatsData.byDomain[domain].rateLimited++;
128
+ this.emitHttp("rateLimited", { domain, retryAfter });
129
+ setTimeout(() => {
130
+ this.domainRateLimited.delete(domain);
131
+ this.emitHttp("domainAvailable", { domain });
132
+ this.tryRunHttpNext();
133
+ }, retryAfter * 1000);
134
+ }
135
+ cancelHttp(id) {
136
+ for (const [domain, queue] of this.domainQueues.entries()) {
137
+ const index = queue.findIndex((t) => t.id === id);
138
+ if (index !== -1) {
139
+ const [task] = queue.splice(index, 1);
140
+ task.reject(new Error("Task was cancelled"));
141
+ this.ensureDomainStats(domain);
142
+ this.httpStatsData.byDomain[domain].failed++;
143
+ this.emitHttp("cancelled", { id });
144
+ return true;
145
+ }
146
+ }
147
+ return false;
148
+ }
149
+ onHttp(event, handler) {
150
+ if (!this.httpEventHandlers.has(event)) {
151
+ this.httpEventHandlers.set(event, new Set);
152
+ }
153
+ this.httpEventHandlers.get(event).add(handler);
154
+ }
155
+ offHttp(event, handler) {
156
+ this.httpEventHandlers.get(event)?.delete(handler);
157
+ }
158
+ clearHttp() {
159
+ for (const [domain, queue] of this.domainQueues.entries()) {
160
+ this.ensureDomainStats(domain);
161
+ for (const task of queue) {
162
+ task.reject(new Error("Queue was cleared"));
163
+ this.httpStatsData.byDomain[domain].failed++;
164
+ this.emitHttp("cancelled", { id: task.id });
165
+ }
166
+ }
167
+ this.domainQueues.clear();
168
+ this.domainPending.clear();
169
+ }
170
+ destroy() {
171
+ this.clearHttp();
172
+ this.httpEventHandlers.clear();
173
+ super.destroy();
174
+ }
175
+ insertHttpTask(task) {
176
+ const domain = task.domain ?? "default";
177
+ if (!this.domainQueues.has(domain)) {
178
+ this.domainQueues.set(domain, []);
179
+ }
180
+ const queue = this.domainQueues.get(domain);
181
+ let insertIndex = queue.length;
182
+ for (let i = 0;i < queue.length; i++) {
183
+ if (task.priority > queue[i].priority) {
184
+ insertIndex = i;
185
+ break;
186
+ }
187
+ }
188
+ queue.splice(insertIndex, 0, task);
189
+ }
190
+ getDomainLimit(domain) {
191
+ const specificLimit = this.domainConcurrencyLimits.get(domain);
192
+ if (specificLimit !== undefined)
193
+ return specificLimit;
194
+ if (typeof this.httpConfig.domainConcurrency === "number") {
195
+ return this.httpConfig.domainConcurrency;
196
+ }
197
+ return 1 / 0;
198
+ }
199
+ canRunDomain(domain) {
200
+ if (this.domainPaused.get(domain))
201
+ return false;
202
+ const rateLimitedUntil = this.domainRateLimited.get(domain);
203
+ if (rateLimitedUntil && Date.now() < rateLimitedUntil)
204
+ return false;
205
+ const pending = this.domainPending.get(domain) ?? 0;
206
+ const limit = this.getDomainLimit(domain);
207
+ return pending < limit;
208
+ }
209
+ tryRunHttpNext() {
210
+ if (this.isPaused)
211
+ return;
212
+ for (const [domain, queue] of this.domainQueues.entries()) {
213
+ if (queue.length === 0)
214
+ continue;
215
+ if (!this.canRunDomain(domain))
216
+ continue;
217
+ const task = queue.shift();
218
+ this.runHttpTask(task);
219
+ }
220
+ }
221
+ async runHttpTask(task) {
222
+ const domain = task.domain ?? "default";
223
+ const pending = (this.domainPending.get(domain) ?? 0) + 1;
224
+ this.domainPending.set(domain, pending);
225
+ this.ensureDomainStats(domain);
226
+ this.httpStatsData.byDomain[domain].pending++;
227
+ this.emitHttp("start", { id: task.id });
228
+ const startTime = Date.now();
229
+ let timeoutId;
230
+ try {
231
+ let result;
232
+ if (task.timeout && task.timeout > 0) {
233
+ result = await Promise.race([
234
+ task.fn(),
235
+ new Promise((_, reject) => {
236
+ timeoutId = setTimeout(() => {
237
+ this.emitHttp("timeout", { id: task.id });
238
+ reject(new Error(`Task ${task.id} timed out after ${task.timeout}ms`));
239
+ }, task.timeout);
240
+ })
241
+ ]);
242
+ } else {
243
+ result = await task.fn();
244
+ }
245
+ if (timeoutId)
246
+ clearTimeout(timeoutId);
247
+ const duration = Date.now() - startTime;
248
+ this.httpStatsData.byDomain[domain].completed++;
249
+ this.emitHttp("completed", { id: task.id, result, duration });
250
+ task.resolve(result);
251
+ } catch (error) {
252
+ if (timeoutId)
253
+ clearTimeout(timeoutId);
254
+ const shouldRetry = task.retryCount < task.maxRetries;
255
+ if (shouldRetry) {
256
+ task.retryCount++;
257
+ this.httpStatsData.retries++;
258
+ const delay = this.getRetryDelay(task);
259
+ this.emitHttp("retry", {
260
+ id: task.id,
261
+ attempt: task.retryCount,
262
+ error
263
+ });
264
+ setTimeout(() => {
265
+ this.insertHttpTask(task);
266
+ this.tryRunHttpNext();
267
+ }, delay);
268
+ } else {
269
+ this.httpStatsData.byDomain[domain].failed++;
270
+ this.emitHttp("error", { id: task.id, error });
271
+ task.reject(error);
272
+ }
273
+ } finally {
274
+ const newPending = (this.domainPending.get(domain) ?? 1) - 1;
275
+ this.domainPending.set(domain, newPending);
276
+ this.httpStatsData.byDomain[domain].pending = newPending;
277
+ this.tryRunHttpNext();
278
+ }
279
+ }
280
+ getRetryDelay(task) {
281
+ if (task.retryDelay !== undefined) {
282
+ return task.retryDelay;
283
+ }
284
+ const baseDelay = typeof this.httpConfig.retryDelay === "function" ? this.httpConfig.retryDelay(task.retryCount) : this.httpConfig.retryDelay;
285
+ return baseDelay * Math.pow(2, task.retryCount - 1);
286
+ }
287
+ ensureDomainStats(domain) {
288
+ if (!this.httpStatsData.byDomain[domain]) {
289
+ this.httpStatsData.byDomain[domain] = {
290
+ pending: 0,
291
+ completed: 0,
292
+ failed: 0,
293
+ rateLimited: 0
294
+ };
295
+ }
296
+ }
297
+ emitHttp(event, data) {
298
+ const handlers = this.httpEventHandlers.get(event);
299
+ if (handlers) {
300
+ for (const handler of handlers) {
301
+ try {
302
+ handler(data);
303
+ } catch {}
304
+ }
305
+ }
306
+ if (event in this.config) {
307
+ this.emit(event, data);
308
+ }
309
+ }
310
+ }
311
+
312
+ export { extractDomain };
@@ -0,0 +1,8 @@
1
+ const _mod_gz0pjo = require('./queue.cjs');
2
+ exports.RezoQueue = _mod_gz0pjo.RezoQueue;;
3
+ const _mod_yjadbd = require('./http-queue.cjs');
4
+ exports.HttpQueue = _mod_yjadbd.HttpQueue;
5
+ exports.extractDomain = _mod_yjadbd.extractDomain;;
6
+ const _mod_h1rr2i = require('./types.cjs');
7
+ exports.Priority = _mod_h1rr2i.Priority;
8
+ exports.HttpMethodPriority = _mod_h1rr2i.HttpMethodPriority;;
@@ -0,0 +1,6 @@
1
+ export { RezoQueue } from './queue.js';
2
+ export { HttpQueue, extractDomain } from './http-queue.js';
3
+ export {
4
+ Priority,
5
+ HttpMethodPriority
6
+ } from './types.js';