@chainlink/external-adapter-framework 2.11.0 → 2.11.1
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/config/index.js +1 -1
- package/generator-adapter/node_modules/.yarn-integrity +8 -8
- package/generator-adapter/node_modules/@types/node/README.md +1 -1
- package/generator-adapter/node_modules/@types/node/package.json +2 -2
- package/generator-adapter/node_modules/@types/node/process.d.ts +7 -0
- package/generator-adapter/node_modules/@yeoman/adapter/dist/queued-adapter.js +1 -3
- package/generator-adapter/node_modules/@yeoman/adapter/dist/queued-adapter.js.map +1 -1
- package/generator-adapter/node_modules/@yeoman/adapter/package.json +3 -3
- package/generator-adapter/node_modules/@yeoman/types/package.json +3 -3
- package/generator-adapter/node_modules/p-queue/dist/index.d.ts +181 -11
- package/generator-adapter/node_modules/p-queue/dist/index.js +366 -45
- package/generator-adapter/node_modules/p-queue/dist/options copy.d.ts +121 -0
- package/generator-adapter/node_modules/p-queue/dist/options copy.js +1 -0
- package/generator-adapter/node_modules/p-queue/dist/options.d.ts +26 -7
- package/generator-adapter/node_modules/p-queue/dist/priority-queue.d.ts +1 -0
- package/generator-adapter/node_modules/p-queue/dist/priority-queue.js +12 -6
- package/generator-adapter/node_modules/p-queue/dist/queue.d.ts +1 -0
- package/generator-adapter/node_modules/p-queue/package.json +19 -27
- package/generator-adapter/node_modules/p-queue/readme.md +541 -19
- package/generator-adapter/node_modules/p-timeout/index.d.ts +2 -4
- package/generator-adapter/node_modules/p-timeout/index.js +23 -50
- package/generator-adapter/node_modules/p-timeout/package.json +11 -9
- package/generator-adapter/node_modules/p-timeout/readme.md +11 -9
- package/generator-adapter/package.json +3 -3
- package/package.json +6 -6
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import { EventEmitter } from 'eventemitter3';
|
|
2
|
-
import pTimeout
|
|
2
|
+
import pTimeout from 'p-timeout';
|
|
3
3
|
import PriorityQueue from './priority-queue.js';
|
|
4
4
|
/**
|
|
5
5
|
Promise queue with concurrency control.
|
|
6
6
|
*/
|
|
7
7
|
export default class PQueue extends EventEmitter {
|
|
8
|
-
#
|
|
8
|
+
#carryoverIntervalCount;
|
|
9
9
|
#isIntervalIgnored;
|
|
10
10
|
#intervalCount = 0;
|
|
11
11
|
#intervalCap;
|
|
12
|
+
#rateLimitedInInterval = false;
|
|
13
|
+
#rateLimitFlushScheduled = false;
|
|
12
14
|
#interval;
|
|
13
15
|
#intervalEnd = 0;
|
|
16
|
+
#lastExecutionTime = 0;
|
|
14
17
|
#intervalId;
|
|
15
18
|
#timeoutId;
|
|
16
19
|
#queue;
|
|
@@ -19,19 +22,31 @@ export default class PQueue extends EventEmitter {
|
|
|
19
22
|
// The `!` is needed because of https://github.com/microsoft/TypeScript/issues/32194
|
|
20
23
|
#concurrency;
|
|
21
24
|
#isPaused;
|
|
22
|
-
|
|
25
|
+
// Use to assign a unique identifier to a promise function, if not explicitly specified
|
|
26
|
+
#idAssigner = 1n;
|
|
27
|
+
// Track currently running tasks for debugging
|
|
28
|
+
#runningTasks = new Map();
|
|
23
29
|
/**
|
|
24
|
-
|
|
30
|
+
Get or set the default timeout for all tasks. Can be changed at runtime.
|
|
25
31
|
|
|
26
|
-
|
|
32
|
+
Operations will throw a `TimeoutError` if they don't complete within the specified time.
|
|
33
|
+
|
|
34
|
+
The timeout begins when the operation is dequeued and starts execution, not while it's waiting in the queue.
|
|
35
|
+
|
|
36
|
+
@example
|
|
37
|
+
```
|
|
38
|
+
const queue = new PQueue({timeout: 5000});
|
|
39
|
+
|
|
40
|
+
// Change timeout for all future tasks
|
|
41
|
+
queue.timeout = 10000;
|
|
42
|
+
```
|
|
27
43
|
*/
|
|
28
44
|
timeout;
|
|
29
|
-
// TODO: The `throwOnTimeout` option should affect the return types of `add()` and `addAll()`
|
|
30
45
|
constructor(options) {
|
|
31
46
|
super();
|
|
32
47
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
33
48
|
options = {
|
|
34
|
-
|
|
49
|
+
carryoverIntervalCount: false,
|
|
35
50
|
intervalCap: Number.POSITIVE_INFINITY,
|
|
36
51
|
interval: 0,
|
|
37
52
|
concurrency: Number.POSITIVE_INFINITY,
|
|
@@ -45,16 +60,21 @@ export default class PQueue extends EventEmitter {
|
|
|
45
60
|
if (options.interval === undefined || !(Number.isFinite(options.interval) && options.interval >= 0)) {
|
|
46
61
|
throw new TypeError(`Expected \`interval\` to be a finite number >= 0, got \`${options.interval?.toString() ?? ''}\` (${typeof options.interval})`);
|
|
47
62
|
}
|
|
48
|
-
this
|
|
63
|
+
// TODO: Remove this fallback in the next major version
|
|
64
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
65
|
+
this.#carryoverIntervalCount = options.carryoverIntervalCount ?? options.carryoverConcurrencyCount ?? false;
|
|
49
66
|
this.#isIntervalIgnored = options.intervalCap === Number.POSITIVE_INFINITY || options.interval === 0;
|
|
50
67
|
this.#intervalCap = options.intervalCap;
|
|
51
68
|
this.#interval = options.interval;
|
|
52
69
|
this.#queue = new options.queueClass();
|
|
53
70
|
this.#queueClass = options.queueClass;
|
|
54
71
|
this.concurrency = options.concurrency;
|
|
72
|
+
if (options.timeout !== undefined && !(Number.isFinite(options.timeout) && options.timeout > 0)) {
|
|
73
|
+
throw new TypeError(`Expected \`timeout\` to be a positive finite number, got \`${options.timeout}\` (${typeof options.timeout})`);
|
|
74
|
+
}
|
|
55
75
|
this.timeout = options.timeout;
|
|
56
|
-
this.#throwOnTimeout = options.throwOnTimeout === true;
|
|
57
76
|
this.#isPaused = options.autoStart === false;
|
|
77
|
+
this.#setupRateLimitTracking();
|
|
58
78
|
}
|
|
59
79
|
get #doesIntervalAllowAnother() {
|
|
60
80
|
return this.#isIntervalIgnored || this.#intervalCount < this.#intervalCap;
|
|
@@ -64,11 +84,14 @@ export default class PQueue extends EventEmitter {
|
|
|
64
84
|
}
|
|
65
85
|
#next() {
|
|
66
86
|
this.#pending--;
|
|
87
|
+
if (this.#pending === 0) {
|
|
88
|
+
this.emit('pendingZero');
|
|
89
|
+
}
|
|
67
90
|
this.#tryToStartAnother();
|
|
68
91
|
this.emit('next');
|
|
69
92
|
}
|
|
70
93
|
#onResumeInterval() {
|
|
71
|
-
this.#onInterval();
|
|
94
|
+
this.#onInterval(); // Already schedules update
|
|
72
95
|
this.#initializeIntervalIfNeeded();
|
|
73
96
|
this.#timeoutId = undefined;
|
|
74
97
|
}
|
|
@@ -77,52 +100,81 @@ export default class PQueue extends EventEmitter {
|
|
|
77
100
|
if (this.#intervalId === undefined) {
|
|
78
101
|
const delay = this.#intervalEnd - now;
|
|
79
102
|
if (delay < 0) {
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
|
|
103
|
+
// If the interval has expired while idle, check if we should enforce the interval
|
|
104
|
+
// from the last task execution. This ensures proper spacing between tasks even
|
|
105
|
+
// when the queue becomes empty and then new tasks are added.
|
|
106
|
+
if (this.#lastExecutionTime > 0) {
|
|
107
|
+
const timeSinceLastExecution = now - this.#lastExecutionTime;
|
|
108
|
+
if (timeSinceLastExecution < this.#interval) {
|
|
109
|
+
// Not enough time has passed since the last task execution
|
|
110
|
+
this.#createIntervalTimeout(this.#interval - timeSinceLastExecution);
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Enough time has passed or no previous execution, allow execution
|
|
115
|
+
this.#intervalCount = (this.#carryoverIntervalCount) ? this.#pending : 0;
|
|
83
116
|
}
|
|
84
117
|
else {
|
|
85
118
|
// Act as the interval is pending
|
|
86
|
-
|
|
87
|
-
this.#timeoutId = setTimeout(() => {
|
|
88
|
-
this.#onResumeInterval();
|
|
89
|
-
}, delay);
|
|
90
|
-
}
|
|
119
|
+
this.#createIntervalTimeout(delay);
|
|
91
120
|
return true;
|
|
92
121
|
}
|
|
93
122
|
}
|
|
94
123
|
return false;
|
|
95
124
|
}
|
|
125
|
+
#createIntervalTimeout(delay) {
|
|
126
|
+
if (this.#timeoutId !== undefined) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
this.#timeoutId = setTimeout(() => {
|
|
130
|
+
this.#onResumeInterval();
|
|
131
|
+
}, delay);
|
|
132
|
+
}
|
|
133
|
+
#clearIntervalTimer() {
|
|
134
|
+
if (this.#intervalId) {
|
|
135
|
+
clearInterval(this.#intervalId);
|
|
136
|
+
this.#intervalId = undefined;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
#clearTimeoutTimer() {
|
|
140
|
+
if (this.#timeoutId) {
|
|
141
|
+
clearTimeout(this.#timeoutId);
|
|
142
|
+
this.#timeoutId = undefined;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
96
145
|
#tryToStartAnother() {
|
|
97
146
|
if (this.#queue.size === 0) {
|
|
98
147
|
// We can clear the interval ("pause")
|
|
99
148
|
// Because we can redo it later ("resume")
|
|
100
|
-
|
|
101
|
-
clearInterval(this.#intervalId);
|
|
102
|
-
}
|
|
103
|
-
this.#intervalId = undefined;
|
|
149
|
+
this.#clearIntervalTimer();
|
|
104
150
|
this.emit('empty');
|
|
105
151
|
if (this.#pending === 0) {
|
|
152
|
+
// Clear timeout as well when completely idle
|
|
153
|
+
this.#clearTimeoutTimer();
|
|
106
154
|
this.emit('idle');
|
|
107
155
|
}
|
|
108
156
|
return false;
|
|
109
157
|
}
|
|
158
|
+
let taskStarted = false;
|
|
110
159
|
if (!this.#isPaused) {
|
|
111
160
|
const canInitializeInterval = !this.#isIntervalPaused;
|
|
112
161
|
if (this.#doesIntervalAllowAnother && this.#doesConcurrentAllowAnother) {
|
|
113
162
|
const job = this.#queue.dequeue();
|
|
114
|
-
|
|
115
|
-
|
|
163
|
+
// Increment interval count immediately to prevent race conditions
|
|
164
|
+
if (!this.#isIntervalIgnored) {
|
|
165
|
+
this.#intervalCount++;
|
|
166
|
+
this.#scheduleRateLimitUpdate();
|
|
116
167
|
}
|
|
117
168
|
this.emit('active');
|
|
169
|
+
this.#lastExecutionTime = Date.now();
|
|
118
170
|
job();
|
|
119
171
|
if (canInitializeInterval) {
|
|
120
172
|
this.#initializeIntervalIfNeeded();
|
|
121
173
|
}
|
|
122
|
-
|
|
174
|
+
taskStarted = true;
|
|
123
175
|
}
|
|
124
176
|
}
|
|
125
|
-
return
|
|
177
|
+
return taskStarted;
|
|
126
178
|
}
|
|
127
179
|
#initializeIntervalIfNeeded() {
|
|
128
180
|
if (this.#isIntervalIgnored || this.#intervalId !== undefined) {
|
|
@@ -135,11 +187,11 @@ export default class PQueue extends EventEmitter {
|
|
|
135
187
|
}
|
|
136
188
|
#onInterval() {
|
|
137
189
|
if (this.#intervalCount === 0 && this.#pending === 0 && this.#intervalId) {
|
|
138
|
-
|
|
139
|
-
this.#intervalId = undefined;
|
|
190
|
+
this.#clearIntervalTimer();
|
|
140
191
|
}
|
|
141
|
-
this.#intervalCount = this.#
|
|
192
|
+
this.#intervalCount = this.#carryoverIntervalCount ? this.#pending : 0;
|
|
142
193
|
this.#processQueue();
|
|
194
|
+
this.#scheduleRateLimitUpdate();
|
|
143
195
|
}
|
|
144
196
|
/**
|
|
145
197
|
Executes all queued functions until it reaches the limit.
|
|
@@ -158,46 +210,118 @@ export default class PQueue extends EventEmitter {
|
|
|
158
210
|
this.#concurrency = newConcurrency;
|
|
159
211
|
this.#processQueue();
|
|
160
212
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
213
|
+
/**
|
|
214
|
+
Updates the priority of a promise function by its id, affecting its execution order. Requires a defined concurrency limit to take effect.
|
|
215
|
+
|
|
216
|
+
For example, this can be used to prioritize a promise function to run earlier.
|
|
217
|
+
|
|
218
|
+
```js
|
|
219
|
+
import PQueue from 'p-queue';
|
|
220
|
+
|
|
221
|
+
const queue = new PQueue({concurrency: 1});
|
|
222
|
+
|
|
223
|
+
queue.add(async () => '🦄', {priority: 1});
|
|
224
|
+
queue.add(async () => '🦀', {priority: 0, id: '🦀'});
|
|
225
|
+
queue.add(async () => '🦄', {priority: 1});
|
|
226
|
+
queue.add(async () => '🦄', {priority: 1});
|
|
227
|
+
|
|
228
|
+
queue.setPriority('🦀', 2);
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
In this case, the promise function with `id: '🦀'` runs second.
|
|
232
|
+
|
|
233
|
+
You can also deprioritize a promise function to delay its execution:
|
|
234
|
+
|
|
235
|
+
```js
|
|
236
|
+
import PQueue from 'p-queue';
|
|
237
|
+
|
|
238
|
+
const queue = new PQueue({concurrency: 1});
|
|
239
|
+
|
|
240
|
+
queue.add(async () => '🦄', {priority: 1});
|
|
241
|
+
queue.add(async () => '🦀', {priority: 1, id: '🦀'});
|
|
242
|
+
queue.add(async () => '🦄');
|
|
243
|
+
queue.add(async () => '🦄', {priority: 0});
|
|
244
|
+
|
|
245
|
+
queue.setPriority('🦀', -1);
|
|
246
|
+
```
|
|
247
|
+
Here, the promise function with `id: '🦀'` executes last.
|
|
248
|
+
*/
|
|
249
|
+
setPriority(id, priority) {
|
|
250
|
+
if (typeof priority !== 'number' || !Number.isFinite(priority)) {
|
|
251
|
+
throw new TypeError(`Expected \`priority\` to be a finite number, got \`${priority}\` (${typeof priority})`);
|
|
252
|
+
}
|
|
253
|
+
this.#queue.setPriority(id, priority);
|
|
167
254
|
}
|
|
168
255
|
async add(function_, options = {}) {
|
|
256
|
+
// In case `id` is not defined.
|
|
257
|
+
options.id ??= (this.#idAssigner++).toString();
|
|
169
258
|
options = {
|
|
170
259
|
timeout: this.timeout,
|
|
171
|
-
throwOnTimeout: this.#throwOnTimeout,
|
|
172
260
|
...options,
|
|
173
261
|
};
|
|
174
262
|
return new Promise((resolve, reject) => {
|
|
263
|
+
// Create a unique symbol for tracking this task
|
|
264
|
+
const taskSymbol = Symbol(`task-${options.id}`);
|
|
175
265
|
this.#queue.enqueue(async () => {
|
|
176
266
|
this.#pending++;
|
|
177
|
-
this
|
|
267
|
+
// Track this running task
|
|
268
|
+
this.#runningTasks.set(taskSymbol, {
|
|
269
|
+
id: options.id,
|
|
270
|
+
priority: options.priority ?? 0, // Match priority-queue default
|
|
271
|
+
startTime: Date.now(),
|
|
272
|
+
timeout: options.timeout,
|
|
273
|
+
});
|
|
274
|
+
let eventListener;
|
|
178
275
|
try {
|
|
179
|
-
|
|
276
|
+
// Check abort signal - if aborted, need to decrement the counter
|
|
277
|
+
// that was incremented in tryToStartAnother
|
|
278
|
+
try {
|
|
279
|
+
options.signal?.throwIfAborted();
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
// Decrement the counter that was already incremented
|
|
283
|
+
if (!this.#isIntervalIgnored) {
|
|
284
|
+
this.#intervalCount--;
|
|
285
|
+
}
|
|
286
|
+
// Clean up tracking before throwing
|
|
287
|
+
this.#runningTasks.delete(taskSymbol);
|
|
288
|
+
throw error;
|
|
289
|
+
}
|
|
180
290
|
let operation = function_({ signal: options.signal });
|
|
181
291
|
if (options.timeout) {
|
|
182
|
-
operation = pTimeout(Promise.resolve(operation), {
|
|
292
|
+
operation = pTimeout(Promise.resolve(operation), {
|
|
293
|
+
milliseconds: options.timeout,
|
|
294
|
+
message: `Task timed out after ${options.timeout}ms (queue has ${this.#pending} running, ${this.#queue.size} waiting)`,
|
|
295
|
+
});
|
|
183
296
|
}
|
|
184
297
|
if (options.signal) {
|
|
185
|
-
|
|
298
|
+
const { signal } = options;
|
|
299
|
+
operation = Promise.race([operation, new Promise((_resolve, reject) => {
|
|
300
|
+
eventListener = () => {
|
|
301
|
+
reject(signal.reason);
|
|
302
|
+
};
|
|
303
|
+
signal.addEventListener('abort', eventListener, { once: true });
|
|
304
|
+
})]);
|
|
186
305
|
}
|
|
187
306
|
const result = await operation;
|
|
188
307
|
resolve(result);
|
|
189
308
|
this.emit('completed', result);
|
|
190
309
|
}
|
|
191
310
|
catch (error) {
|
|
192
|
-
if (error instanceof TimeoutError && !options.throwOnTimeout) {
|
|
193
|
-
resolve();
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
311
|
reject(error);
|
|
197
312
|
this.emit('error', error);
|
|
198
313
|
}
|
|
199
314
|
finally {
|
|
200
|
-
|
|
315
|
+
// Clean up abort event listener
|
|
316
|
+
if (eventListener) {
|
|
317
|
+
options.signal?.removeEventListener('abort', eventListener);
|
|
318
|
+
}
|
|
319
|
+
// Remove from running tasks
|
|
320
|
+
this.#runningTasks.delete(taskSymbol);
|
|
321
|
+
// Use queueMicrotask to prevent deep recursion while maintaining timing
|
|
322
|
+
queueMicrotask(() => {
|
|
323
|
+
this.#next();
|
|
324
|
+
});
|
|
201
325
|
}
|
|
202
326
|
}, options);
|
|
203
327
|
this.emit('add');
|
|
@@ -229,6 +353,10 @@ export default class PQueue extends EventEmitter {
|
|
|
229
353
|
*/
|
|
230
354
|
clear() {
|
|
231
355
|
this.#queue = new this.#queueClass();
|
|
356
|
+
// Note: We don't clear #runningTasks as those tasks are still running
|
|
357
|
+
// They will be removed when they complete in the finally block
|
|
358
|
+
// Force synchronous update since clear() should have immediate effect
|
|
359
|
+
this.#updateRateLimitState();
|
|
232
360
|
}
|
|
233
361
|
/**
|
|
234
362
|
Can be called multiple times. Useful if you for example add additional items at a later time.
|
|
@@ -268,6 +396,74 @@ export default class PQueue extends EventEmitter {
|
|
|
268
396
|
}
|
|
269
397
|
await this.#onEvent('idle');
|
|
270
398
|
}
|
|
399
|
+
/**
|
|
400
|
+
The difference with `.onIdle` is that `.onPendingZero` only waits for currently running tasks to finish, ignoring queued tasks.
|
|
401
|
+
|
|
402
|
+
@returns A promise that settles when all currently running tasks have completed; `queue.pending === 0`.
|
|
403
|
+
*/
|
|
404
|
+
async onPendingZero() {
|
|
405
|
+
if (this.#pending === 0) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
await this.#onEvent('pendingZero');
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
@returns A promise that settles when the queue becomes rate-limited due to intervalCap.
|
|
412
|
+
*/
|
|
413
|
+
async onRateLimit() {
|
|
414
|
+
if (this.isRateLimited) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
await this.#onEvent('rateLimit');
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
@returns A promise that settles when the queue is no longer rate-limited.
|
|
421
|
+
*/
|
|
422
|
+
async onRateLimitCleared() {
|
|
423
|
+
if (!this.isRateLimited) {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
await this.#onEvent('rateLimitCleared');
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
@returns A promise that rejects when any task in the queue errors.
|
|
430
|
+
|
|
431
|
+
Use with `Promise.race([queue.onError(), queue.onIdle()])` to fail fast on the first error while still resolving normally when the queue goes idle.
|
|
432
|
+
|
|
433
|
+
Important: The promise returned by `add()` still rejects. You must handle each `add()` promise (for example, `.catch(() => {})`) to avoid unhandled rejections.
|
|
434
|
+
|
|
435
|
+
@example
|
|
436
|
+
```
|
|
437
|
+
import PQueue from 'p-queue';
|
|
438
|
+
|
|
439
|
+
const queue = new PQueue({concurrency: 2});
|
|
440
|
+
|
|
441
|
+
queue.add(() => fetchData(1)).catch(() => {});
|
|
442
|
+
queue.add(() => fetchData(2)).catch(() => {});
|
|
443
|
+
queue.add(() => fetchData(3)).catch(() => {});
|
|
444
|
+
|
|
445
|
+
// Stop processing on first error
|
|
446
|
+
try {
|
|
447
|
+
await Promise.race([
|
|
448
|
+
queue.onError(),
|
|
449
|
+
queue.onIdle()
|
|
450
|
+
]);
|
|
451
|
+
} catch (error) {
|
|
452
|
+
queue.pause(); // Stop processing remaining tasks
|
|
453
|
+
console.error('Queue failed:', error);
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
*/
|
|
457
|
+
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
|
458
|
+
async onError() {
|
|
459
|
+
return new Promise((_resolve, reject) => {
|
|
460
|
+
const handleError = (error) => {
|
|
461
|
+
this.off('error', handleError);
|
|
462
|
+
reject(error);
|
|
463
|
+
};
|
|
464
|
+
this.on('error', handleError);
|
|
465
|
+
});
|
|
466
|
+
}
|
|
271
467
|
async #onEvent(event, filter) {
|
|
272
468
|
return new Promise(resolve => {
|
|
273
469
|
const listener = () => {
|
|
@@ -307,4 +503,129 @@ export default class PQueue extends EventEmitter {
|
|
|
307
503
|
get isPaused() {
|
|
308
504
|
return this.#isPaused;
|
|
309
505
|
}
|
|
506
|
+
#setupRateLimitTracking() {
|
|
507
|
+
// Only schedule updates when rate limiting is enabled
|
|
508
|
+
if (this.#isIntervalIgnored) {
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
// Wire up to lifecycle events that affect rate limit state
|
|
512
|
+
// Only 'add' and 'next' can actually change rate limit state
|
|
513
|
+
this.on('add', () => {
|
|
514
|
+
if (this.#queue.size > 0) {
|
|
515
|
+
this.#scheduleRateLimitUpdate();
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
this.on('next', () => {
|
|
519
|
+
this.#scheduleRateLimitUpdate();
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
#scheduleRateLimitUpdate() {
|
|
523
|
+
// Skip if rate limiting is not enabled or already scheduled
|
|
524
|
+
if (this.#isIntervalIgnored || this.#rateLimitFlushScheduled) {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
this.#rateLimitFlushScheduled = true;
|
|
528
|
+
queueMicrotask(() => {
|
|
529
|
+
this.#rateLimitFlushScheduled = false;
|
|
530
|
+
this.#updateRateLimitState();
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
#updateRateLimitState() {
|
|
534
|
+
const previous = this.#rateLimitedInInterval;
|
|
535
|
+
const shouldBeRateLimited = !this.#isIntervalIgnored
|
|
536
|
+
&& this.#intervalCount >= this.#intervalCap
|
|
537
|
+
&& this.#queue.size > 0;
|
|
538
|
+
if (shouldBeRateLimited !== previous) {
|
|
539
|
+
this.#rateLimitedInInterval = shouldBeRateLimited;
|
|
540
|
+
this.emit(shouldBeRateLimited ? 'rateLimit' : 'rateLimitCleared');
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
Whether the queue is currently rate-limited due to intervalCap.
|
|
545
|
+
*/
|
|
546
|
+
get isRateLimited() {
|
|
547
|
+
return this.#rateLimitedInInterval;
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
Whether the queue is saturated. Returns `true` when:
|
|
551
|
+
- All concurrency slots are occupied and tasks are waiting, OR
|
|
552
|
+
- The queue is rate-limited and tasks are waiting
|
|
553
|
+
|
|
554
|
+
Useful for detecting backpressure and potential hanging tasks.
|
|
555
|
+
|
|
556
|
+
```js
|
|
557
|
+
import PQueue from 'p-queue';
|
|
558
|
+
|
|
559
|
+
const queue = new PQueue({concurrency: 2});
|
|
560
|
+
|
|
561
|
+
// Backpressure handling
|
|
562
|
+
if (queue.isSaturated) {
|
|
563
|
+
console.log('Queue is saturated, waiting for capacity...');
|
|
564
|
+
await queue.onSizeLessThan(queue.concurrency);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// Monitoring for stuck tasks
|
|
568
|
+
setInterval(() => {
|
|
569
|
+
if (queue.isSaturated) {
|
|
570
|
+
console.warn(`Queue saturated: ${queue.pending} running, ${queue.size} waiting`);
|
|
571
|
+
}
|
|
572
|
+
}, 60000);
|
|
573
|
+
```
|
|
574
|
+
*/
|
|
575
|
+
get isSaturated() {
|
|
576
|
+
return (this.#pending === this.#concurrency && this.#queue.size > 0)
|
|
577
|
+
|| (this.isRateLimited && this.#queue.size > 0);
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
The tasks currently being executed. Each task includes its `id`, `priority`, `startTime`, and `timeout` (if set).
|
|
581
|
+
|
|
582
|
+
Returns an array of task info objects.
|
|
583
|
+
|
|
584
|
+
```js
|
|
585
|
+
import PQueue from 'p-queue';
|
|
586
|
+
|
|
587
|
+
const queue = new PQueue({concurrency: 2});
|
|
588
|
+
|
|
589
|
+
// Add tasks with IDs for better debugging
|
|
590
|
+
queue.add(() => fetchUser(123), {id: 'user-123'});
|
|
591
|
+
queue.add(() => fetchPosts(456), {id: 'posts-456', priority: 1});
|
|
592
|
+
|
|
593
|
+
// Check what's running
|
|
594
|
+
console.log(queue.runningTasks);
|
|
595
|
+
// => [{
|
|
596
|
+
// id: 'user-123',
|
|
597
|
+
// priority: 0,
|
|
598
|
+
// startTime: 1759253001716,
|
|
599
|
+
// timeout: undefined
|
|
600
|
+
// }, {
|
|
601
|
+
// id: 'posts-456',
|
|
602
|
+
// priority: 1,
|
|
603
|
+
// startTime: 1759253001916,
|
|
604
|
+
// timeout: undefined
|
|
605
|
+
// }]
|
|
606
|
+
```
|
|
607
|
+
*/
|
|
608
|
+
get runningTasks() {
|
|
609
|
+
// Return fresh array with fresh objects to prevent mutations
|
|
610
|
+
return [...this.#runningTasks.values()].map(task => ({ ...task }));
|
|
611
|
+
}
|
|
310
612
|
}
|
|
613
|
+
/**
|
|
614
|
+
Error thrown when a task times out.
|
|
615
|
+
|
|
616
|
+
@example
|
|
617
|
+
```
|
|
618
|
+
import PQueue, {TimeoutError} from 'p-queue';
|
|
619
|
+
|
|
620
|
+
const queue = new PQueue({timeout: 1000});
|
|
621
|
+
|
|
622
|
+
try {
|
|
623
|
+
await queue.add(() => someTask());
|
|
624
|
+
} catch (error) {
|
|
625
|
+
if (error instanceof TimeoutError) {
|
|
626
|
+
console.log('Task timed out');
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
```
|
|
630
|
+
*/
|
|
631
|
+
export { TimeoutError } from 'p-timeout';
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { type Queue, type RunFunction } from './queue.js';
|
|
2
|
+
type TimeoutOptions = {
|
|
3
|
+
/**
|
|
4
|
+
Per-operation timeout in milliseconds. Operations will throw a `TimeoutError` if they don't complete within the specified time.
|
|
5
|
+
|
|
6
|
+
The timeout begins when the operation is dequeued and starts execution, not while it's waiting in the queue.
|
|
7
|
+
|
|
8
|
+
@default undefined
|
|
9
|
+
|
|
10
|
+
Can be overridden per task using the `timeout` option in `.add()`:
|
|
11
|
+
|
|
12
|
+
@example
|
|
13
|
+
```
|
|
14
|
+
const queue = new PQueue({timeout: 5000});
|
|
15
|
+
|
|
16
|
+
// This task uses the global 5s timeout
|
|
17
|
+
await queue.add(() => fetchData());
|
|
18
|
+
|
|
19
|
+
// This task has a 10s timeout
|
|
20
|
+
await queue.add(() => slowTask(), {timeout: 10000});
|
|
21
|
+
```
|
|
22
|
+
*/
|
|
23
|
+
timeout?: number;
|
|
24
|
+
};
|
|
25
|
+
export type Options<QueueType extends Queue<RunFunction, QueueOptions>, QueueOptions extends QueueAddOptions> = {
|
|
26
|
+
/**
|
|
27
|
+
Concurrency limit.
|
|
28
|
+
|
|
29
|
+
Minimum: `1`.
|
|
30
|
+
|
|
31
|
+
@default Infinity
|
|
32
|
+
*/
|
|
33
|
+
readonly concurrency?: number;
|
|
34
|
+
/**
|
|
35
|
+
Whether queue tasks within concurrency limit, are auto-executed as soon as they're added.
|
|
36
|
+
|
|
37
|
+
@default true
|
|
38
|
+
*/
|
|
39
|
+
readonly autoStart?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
Class with a `enqueue` and `dequeue` method, and a `size` getter. See the [Custom QueueClass](https://github.com/sindresorhus/p-queue#custom-queueclass) section.
|
|
42
|
+
*/
|
|
43
|
+
readonly queueClass?: new () => QueueType;
|
|
44
|
+
/**
|
|
45
|
+
The max number of runs in the given interval of time.
|
|
46
|
+
|
|
47
|
+
Minimum: `1`.
|
|
48
|
+
|
|
49
|
+
@default Infinity
|
|
50
|
+
*/
|
|
51
|
+
readonly intervalCap?: number;
|
|
52
|
+
/**
|
|
53
|
+
The length of time in milliseconds before the interval count resets. Must be finite.
|
|
54
|
+
|
|
55
|
+
Minimum: `0`.
|
|
56
|
+
|
|
57
|
+
@default 0
|
|
58
|
+
*/
|
|
59
|
+
readonly interval?: number;
|
|
60
|
+
/**
|
|
61
|
+
Whether the task must finish in the given interval or will be carried over into the next interval count.
|
|
62
|
+
|
|
63
|
+
@default false
|
|
64
|
+
*/
|
|
65
|
+
readonly carryoverIntervalCount?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
@deprecated Renamed to `carryoverIntervalCount`.
|
|
68
|
+
*/
|
|
69
|
+
readonly carryoverConcurrencyCount?: boolean;
|
|
70
|
+
} & TimeoutOptions;
|
|
71
|
+
export type QueueAddOptions = {
|
|
72
|
+
/**
|
|
73
|
+
Priority of operation. Operations with greater priority will be scheduled first.
|
|
74
|
+
|
|
75
|
+
@default 0
|
|
76
|
+
*/
|
|
77
|
+
readonly priority?: number;
|
|
78
|
+
/**
|
|
79
|
+
Unique identifier for the promise function, used to update its priority before execution. If not specified, it is auto-assigned an incrementing BigInt starting from `1n`.
|
|
80
|
+
*/
|
|
81
|
+
id?: string;
|
|
82
|
+
} & TaskOptions & TimeoutOptions;
|
|
83
|
+
export type TaskOptions = {
|
|
84
|
+
/**
|
|
85
|
+
[`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) for cancellation of the operation. When aborted, it will be removed from the queue and the `queue.add()` call will reject with an `AbortError`. If the operation is already running, the signal will need to be handled by the operation itself.
|
|
86
|
+
|
|
87
|
+
@example
|
|
88
|
+
```
|
|
89
|
+
import PQueue, {AbortError} from 'p-queue';
|
|
90
|
+
import got, {CancelError} from 'got';
|
|
91
|
+
|
|
92
|
+
const queue = new PQueue();
|
|
93
|
+
|
|
94
|
+
const controller = new AbortController();
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
await queue.add(({signal}) => {
|
|
98
|
+
const request = got('https://sindresorhus.com');
|
|
99
|
+
|
|
100
|
+
signal.addEventListener('abort', () => {
|
|
101
|
+
request.cancel();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
return await request;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
if (!(error instanceof CancelError)) {
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}, {signal: controller.signal});
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if (!(error instanceof AbortError)) {
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
*/
|
|
119
|
+
readonly signal?: AbortSignal;
|
|
120
|
+
};
|
|
121
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|