rezo 1.0.5 → 1.0.7

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 (49) 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 +2407 -20
  5. package/dist/adapters/entries/fetch.d.ts +364 -20
  6. package/dist/adapters/entries/http.d.ts +364 -20
  7. package/dist/adapters/entries/http2.d.ts +364 -20
  8. package/dist/adapters/entries/react-native.d.ts +364 -20
  9. package/dist/adapters/entries/xhr.d.ts +364 -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/hooks.cjs +2 -0
  15. package/dist/core/hooks.js +2 -0
  16. package/dist/core/rezo.cjs +2 -2
  17. package/dist/core/rezo.js +2 -2
  18. package/dist/crawler.d.ts +366 -22
  19. package/dist/entries/crawler.cjs +5 -5
  20. package/dist/index.cjs +23 -18
  21. package/dist/index.d.ts +631 -20
  22. package/dist/index.js +1 -0
  23. package/dist/platform/browser.d.ts +364 -20
  24. package/dist/platform/bun.d.ts +364 -20
  25. package/dist/platform/deno.d.ts +364 -20
  26. package/dist/platform/node.d.ts +364 -20
  27. package/dist/platform/react-native.d.ts +364 -20
  28. package/dist/platform/worker.d.ts +364 -20
  29. package/dist/plugin/crawler-options.cjs +1 -1
  30. package/dist/plugin/crawler-options.js +1 -1
  31. package/dist/plugin/crawler.cjs +2 -2
  32. package/dist/plugin/crawler.js +2 -2
  33. package/dist/plugin/index.cjs +36 -36
  34. package/dist/proxy/index.cjs +2 -2
  35. package/dist/proxy/manager.cjs +57 -2
  36. package/dist/proxy/manager.js +57 -2
  37. package/dist/queue/http-queue.cjs +313 -0
  38. package/dist/queue/http-queue.js +312 -0
  39. package/dist/queue/index.cjs +8 -0
  40. package/dist/queue/index.js +6 -0
  41. package/dist/queue/queue.cjs +346 -0
  42. package/dist/queue/queue.js +344 -0
  43. package/dist/queue/types.cjs +17 -0
  44. package/dist/queue/types.js +17 -0
  45. package/dist/types/curl-options.cjs +25 -0
  46. package/dist/types/curl-options.js +25 -0
  47. package/dist/utils/http-config.cjs +0 -15
  48. package/dist/utils/http-config.js +0 -15
  49. package/package.json +1 -2
@@ -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_1hdvgg = require('./queue.cjs');
2
+ exports.RezoQueue = _mod_1hdvgg.RezoQueue;;
3
+ const _mod_89rege = require('./http-queue.cjs');
4
+ exports.HttpQueue = _mod_89rege.HttpQueue;
5
+ exports.extractDomain = _mod_89rege.extractDomain;;
6
+ const _mod_bw5pkd = require('./types.cjs');
7
+ exports.Priority = _mod_bw5pkd.Priority;
8
+ exports.HttpMethodPriority = _mod_bw5pkd.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';
@@ -0,0 +1,346 @@
1
+ function generateId() {
2
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 9)}`;
3
+ }
4
+
5
+ class RezoQueue {
6
+ queue = [];
7
+ pendingCount = 0;
8
+ isPausedFlag = false;
9
+ intervalId;
10
+ intervalCount = 0;
11
+ intervalStart = 0;
12
+ eventHandlers = new Map;
13
+ statsData = {
14
+ added: 0,
15
+ processed: 0,
16
+ completed: 0,
17
+ failed: 0,
18
+ timedOut: 0,
19
+ cancelled: 0,
20
+ averageDuration: 0,
21
+ throughput: 0
22
+ };
23
+ totalDuration = 0;
24
+ throughputWindow = [];
25
+ throughputWindowSize = 60;
26
+ idlePromise;
27
+ emptyPromise;
28
+ config;
29
+ constructor(config = {}) {
30
+ this.config = {
31
+ concurrency: config.concurrency ?? 1 / 0,
32
+ autoStart: config.autoStart ?? true,
33
+ timeout: config.timeout ?? 0,
34
+ throwOnTimeout: config.throwOnTimeout ?? true,
35
+ interval: config.interval ?? 0,
36
+ intervalCap: config.intervalCap ?? 1 / 0,
37
+ carryoverConcurrencyCount: config.carryoverConcurrencyCount ?? false
38
+ };
39
+ if (!this.config.autoStart) {
40
+ this.isPausedFlag = true;
41
+ }
42
+ if (this.config.interval > 0) {
43
+ this.startInterval();
44
+ }
45
+ }
46
+ get state() {
47
+ return {
48
+ pending: this.pendingCount,
49
+ size: this.queue.length,
50
+ total: this.pendingCount + this.queue.length,
51
+ isPaused: this.isPausedFlag,
52
+ isIdle: this.pendingCount === 0 && this.queue.length === 0
53
+ };
54
+ }
55
+ get stats() {
56
+ return { ...this.statsData };
57
+ }
58
+ get concurrency() {
59
+ return this.config.concurrency;
60
+ }
61
+ set concurrency(value) {
62
+ this.config.concurrency = value;
63
+ this.tryRunNext();
64
+ }
65
+ get pending() {
66
+ return this.pendingCount;
67
+ }
68
+ get size() {
69
+ return this.queue.length;
70
+ }
71
+ get isPaused() {
72
+ return this.isPausedFlag;
73
+ }
74
+ add(fn, options = {}) {
75
+ return new Promise((resolve, reject) => {
76
+ const task = {
77
+ id: options.id ?? generateId(),
78
+ fn,
79
+ priority: options.priority ?? 0,
80
+ timeout: options.timeout ?? this.config.timeout,
81
+ signal: options.signal,
82
+ resolve,
83
+ reject,
84
+ addedAt: Date.now()
85
+ };
86
+ if (options.signal?.aborted) {
87
+ reject(new Error("Task was cancelled before starting"));
88
+ return;
89
+ }
90
+ options.signal?.addEventListener("abort", () => {
91
+ this.cancel(task.id);
92
+ });
93
+ this.insertByPriority(task);
94
+ this.statsData.added++;
95
+ this.emit("add", { id: task.id, priority: task.priority });
96
+ if (this.config.autoStart && !this.isPausedFlag) {
97
+ this.tryRunNext();
98
+ }
99
+ });
100
+ }
101
+ addAll(fns, options = {}) {
102
+ return Promise.all(fns.map((fn, i) => this.add(fn, { ...options, id: options.id ? `${options.id}-${i}` : undefined })));
103
+ }
104
+ pause() {
105
+ if (!this.isPausedFlag) {
106
+ this.isPausedFlag = true;
107
+ this.emit("paused", undefined);
108
+ }
109
+ }
110
+ start() {
111
+ if (this.isPausedFlag) {
112
+ this.isPausedFlag = false;
113
+ this.emit("resumed", undefined);
114
+ this.tryRunNext();
115
+ }
116
+ }
117
+ clear() {
118
+ const tasks = [...this.queue];
119
+ this.queue = [];
120
+ for (const task of tasks) {
121
+ task.reject(new Error("Queue was cleared"));
122
+ this.statsData.cancelled++;
123
+ this.emit("cancelled", { id: task.id });
124
+ }
125
+ this.checkEmpty();
126
+ }
127
+ cancel(id) {
128
+ const index = this.queue.findIndex((t) => t.id === id);
129
+ if (index !== -1) {
130
+ const [task] = this.queue.splice(index, 1);
131
+ task.reject(new Error("Task was cancelled"));
132
+ this.statsData.cancelled++;
133
+ this.emit("cancelled", { id });
134
+ this.checkEmpty();
135
+ return true;
136
+ }
137
+ return false;
138
+ }
139
+ cancelBy(predicate) {
140
+ let count = 0;
141
+ const remaining = [];
142
+ for (const task of this.queue) {
143
+ if (predicate({ id: task.id, priority: task.priority })) {
144
+ task.reject(new Error("Task was cancelled"));
145
+ this.statsData.cancelled++;
146
+ this.emit("cancelled", { id: task.id });
147
+ count++;
148
+ } else {
149
+ remaining.push(task);
150
+ }
151
+ }
152
+ this.queue = remaining;
153
+ if (count > 0) {
154
+ this.checkEmpty();
155
+ }
156
+ return count;
157
+ }
158
+ onIdle() {
159
+ if (this.state.isIdle) {
160
+ return Promise.resolve();
161
+ }
162
+ if (!this.idlePromise) {
163
+ let resolvePromise;
164
+ const promise = new Promise((resolve) => {
165
+ resolvePromise = resolve;
166
+ });
167
+ this.idlePromise = { promise, resolve: resolvePromise };
168
+ }
169
+ return this.idlePromise.promise;
170
+ }
171
+ onEmpty() {
172
+ if (this.queue.length === 0) {
173
+ return Promise.resolve();
174
+ }
175
+ if (!this.emptyPromise) {
176
+ let resolvePromise;
177
+ const promise = new Promise((resolve) => {
178
+ resolvePromise = resolve;
179
+ });
180
+ this.emptyPromise = { promise, resolve: resolvePromise };
181
+ }
182
+ return this.emptyPromise.promise;
183
+ }
184
+ onSizeLessThan(limit) {
185
+ if (this.queue.length < limit) {
186
+ return Promise.resolve();
187
+ }
188
+ return new Promise((resolve) => {
189
+ const check = () => {
190
+ if (this.queue.length < limit) {
191
+ this.off("completed", check);
192
+ this.off("cancelled", check);
193
+ this.off("error", check);
194
+ resolve();
195
+ }
196
+ };
197
+ this.on("completed", check);
198
+ this.on("cancelled", check);
199
+ this.on("error", check);
200
+ });
201
+ }
202
+ on(event, handler) {
203
+ if (!this.eventHandlers.has(event)) {
204
+ this.eventHandlers.set(event, new Set);
205
+ }
206
+ this.eventHandlers.get(event).add(handler);
207
+ }
208
+ off(event, handler) {
209
+ this.eventHandlers.get(event)?.delete(handler);
210
+ }
211
+ destroy() {
212
+ this.clear();
213
+ if (this.intervalId) {
214
+ clearInterval(this.intervalId);
215
+ this.intervalId = undefined;
216
+ }
217
+ this.eventHandlers.clear();
218
+ }
219
+ insertByPriority(task) {
220
+ let insertIndex = this.queue.length;
221
+ for (let i = 0;i < this.queue.length; i++) {
222
+ if (task.priority > this.queue[i].priority) {
223
+ insertIndex = i;
224
+ break;
225
+ }
226
+ }
227
+ this.queue.splice(insertIndex, 0, task);
228
+ }
229
+ tryRunNext() {
230
+ if (this.isPausedFlag)
231
+ return;
232
+ if (this.queue.length === 0)
233
+ return;
234
+ if (this.pendingCount >= this.config.concurrency)
235
+ return;
236
+ if (this.config.interval > 0) {
237
+ if (this.intervalCount >= this.config.intervalCap)
238
+ return;
239
+ this.intervalCount++;
240
+ }
241
+ const task = this.queue.shift();
242
+ this.runTask(task);
243
+ this.tryRunNext();
244
+ }
245
+ async runTask(task) {
246
+ this.pendingCount++;
247
+ this.statsData.processed++;
248
+ const wasIdle = this.pendingCount === 1 && this.queue.length === 0;
249
+ if (wasIdle) {
250
+ this.emit("active", undefined);
251
+ }
252
+ this.emit("start", { id: task.id });
253
+ this.emit("next", undefined);
254
+ const startTime = Date.now();
255
+ let timeoutId;
256
+ try {
257
+ let result;
258
+ if (task.timeout && task.timeout > 0) {
259
+ result = await Promise.race([
260
+ task.fn(),
261
+ new Promise((_, reject) => {
262
+ timeoutId = setTimeout(() => {
263
+ this.statsData.timedOut++;
264
+ this.emit("timeout", { id: task.id });
265
+ if (this.config.throwOnTimeout) {
266
+ reject(new Error(`Task ${task.id} timed out after ${task.timeout}ms`));
267
+ }
268
+ }, task.timeout);
269
+ })
270
+ ]);
271
+ } else {
272
+ result = await task.fn();
273
+ }
274
+ if (timeoutId)
275
+ clearTimeout(timeoutId);
276
+ const duration = Date.now() - startTime;
277
+ this.recordDuration(duration);
278
+ this.statsData.completed++;
279
+ this.emit("completed", { id: task.id, result, duration });
280
+ task.resolve(result);
281
+ } catch (error) {
282
+ if (timeoutId)
283
+ clearTimeout(timeoutId);
284
+ this.statsData.failed++;
285
+ this.emit("error", { id: task.id, error });
286
+ task.reject(error);
287
+ } finally {
288
+ this.pendingCount--;
289
+ this.checkEmpty();
290
+ this.checkIdle();
291
+ this.tryRunNext();
292
+ }
293
+ }
294
+ recordDuration(duration) {
295
+ this.totalDuration += duration;
296
+ this.statsData.averageDuration = this.totalDuration / this.statsData.completed;
297
+ const now = Date.now();
298
+ this.throughputWindow.push(now);
299
+ const windowStart = now - this.throughputWindowSize * 1000;
300
+ this.throughputWindow = this.throughputWindow.filter((t) => t > windowStart);
301
+ this.statsData.throughput = this.throughputWindow.length / this.throughputWindowSize;
302
+ }
303
+ startInterval() {
304
+ this.intervalStart = Date.now();
305
+ this.intervalCount = 0;
306
+ this.intervalId = setInterval(() => {
307
+ if (!this.config.carryoverConcurrencyCount) {
308
+ this.intervalCount = 0;
309
+ } else {
310
+ this.intervalCount = Math.max(0, this.intervalCount - this.config.intervalCap);
311
+ }
312
+ this.intervalStart = Date.now();
313
+ this.tryRunNext();
314
+ }, this.config.interval);
315
+ }
316
+ emit(event, data) {
317
+ const handlers = this.eventHandlers.get(event);
318
+ if (handlers) {
319
+ for (const handler of handlers) {
320
+ try {
321
+ handler(data);
322
+ } catch {}
323
+ }
324
+ }
325
+ }
326
+ checkEmpty() {
327
+ if (this.queue.length === 0) {
328
+ this.emit("empty", undefined);
329
+ if (this.emptyPromise) {
330
+ this.emptyPromise.resolve();
331
+ this.emptyPromise = undefined;
332
+ }
333
+ }
334
+ }
335
+ checkIdle() {
336
+ if (this.state.isIdle) {
337
+ this.emit("idle", undefined);
338
+ if (this.idlePromise) {
339
+ this.idlePromise.resolve();
340
+ this.idlePromise = undefined;
341
+ }
342
+ }
343
+ }
344
+ }
345
+
346
+ exports.RezoQueue = RezoQueue;