dynamic-throttled-queue 2.3.0-rc.494c954 → 2.3.0-rc.78d8bef
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -2
- package/dist/adaptive-rate.d.ts +43 -6
- package/dist/adaptive-rate.js +70 -11
- package/dist/adaptive-rate.js.map +1 -1
- package/dist/dynamic-throttled-queue.d.ts +7 -59
- package/dist/dynamic-throttled-queue.js +20 -33
- package/dist/dynamic-throttled-queue.js.map +1 -1
- package/dist/pending-work.d.ts +2 -0
- package/dist/pending-work.js +31 -13
- package/dist/pending-work.js.map +1 -1
- package/dist/retry-policy.d.ts +1 -1
- package/dist/scheduler.d.ts +61 -2
- package/dist/scheduler.js +159 -28
- package/dist/scheduler.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ throttle(() => {
|
|
|
28
28
|
|
|
29
29
|
Callbacks can return `false` to signal a failure (used for dynamic rate adjustment and retry). Async callbacks (returning a Promise) are also supported — rejections and `false` resolutions count as failures. By default, every failure reduces the adaptive rate; use `rateOutcomeClassifier` when only selected failures should do so. Use `retryClassifier` to separately decide whether a failure is eligible for retry.
|
|
30
30
|
|
|
31
|
-
Each callback receives an execution context containing the queue-owned `
|
|
31
|
+
Each callback receives an execution context containing an `AbortSignal` and the one-based `attempt` number (retries count up from 1). Fire-and-forget callbacks get the queue-owned signal, which only `abort()` aborts. Callbacks passed to [`submit()`](#task-handles) get a signal scoped to their item instead. Existing zero-argument callbacks remain supported. Use the signal to cooperatively cancel in-flight work:
|
|
32
32
|
|
|
33
33
|
```ts
|
|
34
34
|
throttle(async ({ signal }) => {
|
|
@@ -41,6 +41,29 @@ Set `concurrency` to bound callbacks that are still awaiting asynchronous comple
|
|
|
41
41
|
|
|
42
42
|
Set `maxQueueSize` to bound accepted work. Capacity is reserved from enqueue through terminal success or failure, including pending callbacks, active callbacks, and retries. A full queue makes `enqueue()` throw synchronously in v2; unlimited capacity remains the default. In v3, a full queue will return `false` instead.
|
|
43
43
|
|
|
44
|
+
### Task handles
|
|
45
|
+
|
|
46
|
+
`submit()` accepts a callback like the fire-and-forget form, but returns a handle for that one item, covering every attempt it makes:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
const task = queue.submit(async ({ signal, attempt }) => {
|
|
50
|
+
const response = await fetch("/api/data", { signal });
|
|
51
|
+
if (!response.ok) throw new Error(`HTTP ${response.status} on attempt ${attempt}`);
|
|
52
|
+
return response.json();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
task.cancel();
|
|
56
|
+
const data = await task.result;
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
- **Results.** Every value the callback returns or resolves, including `false`, is the result. Only a throw or a rejection is a failure, and it goes through `retry`, `retryClassifier`, and `rateOutcomeClassifier` the same as any other failure. `result` fulfills once, with the final successful value, or rejects once, with the final attempt's error after retries end.
|
|
60
|
+
- **Cancellation.** `cancel(reason?)` rejects `result` immediately with `reason`, or with an `AbortError` when no reason is given. A canceled item that has not started (including one waiting on a delayed retry) is removed: it never starts, frees its `maxQueueSize` slot, and does not use a start slot. A canceled active item has its own signal aborted, and only that signal; other work and the queue signal are unaffected. It keeps its concurrency slot until the callback returns, and `waitForIdle()` waits for it. Its outcome never retries and never counts as a success, a failure, or a rate-reducing error. Calling `cancel()` again, or after `result` has settled, does nothing.
|
|
61
|
+
- **Queue termination.** `abort()` rejects every unsettled item at once with the queue signal's `AbortError` and aborts each item's signal. A strategy failure (see [Lifecycle](#lifecycle)) does the same with the strategy error. Values that arrive later are discarded.
|
|
62
|
+
- **Retained work.** `pause()` and `stop()` keep items and leave their results pending.
|
|
63
|
+
- **Admission.** `submit()` throws synchronously and returns no handle when `enqueue()` would throw: the queue is full, aborted, or failed.
|
|
64
|
+
|
|
65
|
+
`result` is an ordinary promise. If you cancel or abort without awaiting it, attach a handler (for example `task.result.catch(() => {})`) so the rejection is not reported as unhandled.
|
|
66
|
+
|
|
44
67
|
## Options
|
|
45
68
|
|
|
46
69
|
| Param | Type | Default | Description |
|
|
@@ -55,6 +78,7 @@ Set `maxQueueSize` to bound accepted work. Capacity is reserved from enqueue thr
|
|
|
55
78
|
| `retryBackoff` | `RetryBackoff` | — | Per-retry fixed, linear, or exponential delay policy; omit for immediate retries |
|
|
56
79
|
| `concurrency` | `number` | — | Maximum callbacks awaiting asynchronous completion; omit for no limit |
|
|
57
80
|
| `maxQueueSize` | `number` | — | Maximum accepted callbacks not yet terminal, including pending, active, and retried work; omit for no limit |
|
|
81
|
+
| `maxCooldown` | `number` | `2147483647` | Longest cooldown, in milliseconds, that `cooldownFor()` applies; longer requests are clamped. Must be positive and at most `2147483647`, the largest delay timers honor |
|
|
58
82
|
| `compact_threshold` | `number` | `512` | Non-negative integer minimum dead slots before internal queue compaction triggers; `0` compacts at the earliest eligible point |
|
|
59
83
|
| `rateStrategy` | `RateStrategy` | `linear` | Pure policy that requests the next rate and an optional backoff after each observation window |
|
|
60
84
|
| `rateOutcomeClassifier` | `RateOutcomeClassifier` | — | Decides whether a failed callback outcome contributes to adaptive-rate error counting |
|
|
@@ -78,10 +102,12 @@ With `adjustmentTiming: "settled"`, an observation window contains every callbac
|
|
|
78
102
|
|
|
79
103
|
| Property | Type | Description |
|
|
80
104
|
| -------- | ---- | ----------- |
|
|
105
|
+
| `submit(callback)` | `<T>(callback) => TaskHandle<T>` | Enqueue one item and return its `{ result, cancel }` handle; see [Task handles](#task-handles) |
|
|
81
106
|
| `pause()` | `() => void` | Temporarily prevent new callback starts while retaining accepted work |
|
|
82
107
|
| `resume()` | `() => void` | Resume a paused queue with a fresh pacing and observation window |
|
|
83
108
|
| `stop()` | `() => void` | Stop processing the queue immediately |
|
|
84
109
|
| `abort()` | `() => void` | Terminally discard queued work and signal active callbacks |
|
|
110
|
+
| `cooldownFor(delay)` | `(delay: number) => void` | Start nothing new for `delay` ms, for example after a server's `Retry-After`; see [Server-directed cooldown](#server-directed-cooldown) |
|
|
85
111
|
| `waitForIdle()` | `() => Promise<void>` | Resolves when all pending, active, and delayed-retry work has completed |
|
|
86
112
|
| `getState()` | `() => QueueState` | Returns a frozen point-in-time snapshot of queue state and counters |
|
|
87
113
|
| `pending` | `number` (readonly) | Number of callbacks waiting in the queue, including delayed retries |
|
|
@@ -115,7 +141,7 @@ In settled timing, `pause()` discards the in-progress observation window rather
|
|
|
115
141
|
|
|
116
142
|
A queue fails when `rateStrategy` throws or returns a malformed decision. The queue clears its timers, discards pending callbacks and delayed retries, and reports `state: "failed"`. The original error is still rethrown where the decision ran: an uncaught exception from the adaptive-rate timer, or an unhandled rejection when a slow asynchronous callback completes a settled-timing window. Pending `waitForIdle()` promises reject with that error immediately, even while callbacks are still active; later `waitForIdle()` calls reject with it and later enqueues throw it. Unlike `abort()`, failure does not wait for active callbacks to settle. Create a new queue to resume work.
|
|
117
143
|
|
|
118
|
-
`getState()` returns a frozen `QueueState` snapshot. Each call is independent; mutations to the returned object do not affect the queue. Fields: `rate` (current rate), `pending` (queued + delayed retries), `active` (callbacks executing), `state` (`"running"` | `"paused"` | `"stopped"` | `"aborted"` | `"failed"`), and monotonic lifetime counters `started`, `succeeded`, `failed`, `retried`, `rateIncreases`, `rateDecreases`. `started`/`succeeded`/`failed` count callback attempts: a retry is a new attempt; a failure is counted even when a later retry succeeds. `retried` increments only when another attempt is actually scheduled. Rate-direction counters match applied rate changes and `onRateChange` notifications exactly. Counters do not change for settlements after `abort()` or a strategy failure.
|
|
144
|
+
`getState()` returns a frozen `QueueState` snapshot. Each call is independent; mutations to the returned object do not affect the queue. Fields: `rate` (current rate), `pending` (queued + delayed retries), `active` (callbacks executing), `state` (`"running"` | `"paused"` | `"stopped"` | `"aborted"` | `"failed"`), `cooldownRemaining` (milliseconds, rounded up, until an active cooldown ends; `0` when none is active), and monotonic lifetime counters `started`, `succeeded`, `failed`, `retried`, `canceled`, `rateIncreases`, `rateDecreases`, `cooldowns`, `cooldownTotal`. `started`/`succeeded`/`failed` count callback attempts: a retry is a new attempt; a failure is counted even when a later retry succeeds. `retried` increments only when another attempt is actually scheduled. `canceled` counts submitted items whose result `cancel()` rejected; a canceled active attempt counts as neither succeeded nor failed. Rate-direction counters match applied rate changes and `onRateChange` notifications exactly. `cooldowns` counts `cooldownFor()` calls that moved the deadline later, and `cooldownTotal` is the milliseconds they added. Counters do not change for settlements after `abort()` or a strategy failure.
|
|
119
145
|
|
|
120
146
|
`waitForIdle()` returns a `Promise<void>` that resolves once all pending callbacks, active executions, and delayed retries have reached a terminal outcome. If the queue is already idle the promise resolves immediately. Each call is one-shot: a later enqueue does not affect a promise that has already resolved. Multiple simultaneous callers all resolve at the same idle transition without retaining waiter state. A callback failure that triggers a retry never exposes a transient idle transition between the failed attempt and the retry. `stop()` retains pending work, so existing waiters remain pending until that work completes after a later enqueue resumes the queue. `abort()` discards pending work, so waiters resolve as soon as no callback is active: immediately when none is running, otherwise when the last active callback settles. Post-abort settlements do not create new retry work. A paused queue resolves waiters only when both pending and active work are zero. A failed queue rejects every waiter with the strategy error at once, without waiting for active callbacks.
|
|
121
147
|
|
|
@@ -309,6 +335,31 @@ const throttle = createThrottledQueue({
|
|
|
309
335
|
|
|
310
336
|
Delayed retries remain pending, return to the normal queue tail when due, and then obey normal scheduler pacing. Equal due times preserve the order their retry delays were scheduled. `abort()` discards delayed retries along with other pending work.
|
|
311
337
|
|
|
338
|
+
### Server-directed cooldown
|
|
339
|
+
|
|
340
|
+
`cooldownFor(delay)` stops the queue from starting anything new for `delay` milliseconds. Use it when a server says when capacity returns, such as HTTP 429 with `Retry-After`. The queue does not read HTTP responses: convert the hint to milliseconds yourself, clamping past dates to `0`.
|
|
341
|
+
|
|
342
|
+
```ts
|
|
343
|
+
const throttle = createThrottledQueue({ min_rpi: 1, max_rpi: 10, interval: 1000, maxCooldown: 60_000 });
|
|
344
|
+
|
|
345
|
+
throttle.submit(async () => {
|
|
346
|
+
const res = await fetch("https://api.example.com/data");
|
|
347
|
+
if (res.status === 429) {
|
|
348
|
+
const seconds = Number(res.headers.get("Retry-After"));
|
|
349
|
+
if (Number.isFinite(seconds) && seconds >= 0) throttle.cooldownFor(seconds * 1000);
|
|
350
|
+
throw new Error("rate limited");
|
|
351
|
+
}
|
|
352
|
+
return res.json();
|
|
353
|
+
});
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
- **Scheduling.** Pending work, new enqueues, and retries are kept and do not start. Active callbacks keep running and settle normally. When the cooldown ends, starts resume without a new enqueue; the first one is due one spacing later, as after any fresh start. Delayed retries keep counting down and join the queue when due.
|
|
357
|
+
- **Overlaps.** Each call asks for "nothing before now + `delay`". A later deadline replaces the current one; an earlier one is ignored. Two 60-second hints that arrive together give one 60-second cooldown, not two.
|
|
358
|
+
- **Validation.** `NaN`, negative, and infinite delays throw a `RangeError` in every lifecycle state, including after `abort()` or a strategy failure. Delays above `maxCooldown` are clamped to it. `0` does nothing.
|
|
359
|
+
- **Lifecycle.** `pause()` and cooldown are separate: a cooldown keeps elapsing while paused, its end does not resume a paused queue, and `resume()` does not end it. `stop()` clears the cooldown timer but keeps the deadline, so the enqueue that restarts the queue still waits for it. `abort()` and a strategy failure clear the cooldown; later valid calls do nothing.
|
|
360
|
+
- **Adaptive rate.** No rate decision is made while cooling down, so quiet intervals do not raise the rate. Failures that settle during the cooldown still count toward the first decision after it. In interval timing, a fresh interval begins when the cooldown ends. In settled timing, a cooldown closes the open collection interval early; once it ends and every callback started in that window has settled, the queue decides that window and opens the next. A cooldown replaces any adaptive-rate backoff already in progress. Outcomes that settle while paused stay ignored, as without a cooldown.
|
|
361
|
+
- **Separate decisions.** A cooldown does not reduce the rate or retry anything. Use `rateOutcomeClassifier` and `retryClassifier` for those; the three can be combined freely.
|
|
362
|
+
|
|
312
363
|
## Migration from v1
|
|
313
364
|
|
|
314
365
|
- `errors_per_second` removed — use `errors_per_interval` (counts errors per full interval window, not per second).
|
package/dist/adaptive-rate.d.ts
CHANGED
|
@@ -1,14 +1,44 @@
|
|
|
1
|
-
|
|
1
|
+
export type RateStrategyObservation = Readonly<{
|
|
2
|
+
currentRate: number;
|
|
3
|
+
minRate: number;
|
|
4
|
+
maxRate: number;
|
|
5
|
+
errorCount: number;
|
|
6
|
+
errorThreshold: number;
|
|
7
|
+
hasPendingWork: boolean;
|
|
8
|
+
wasBackedOff: boolean;
|
|
9
|
+
}>;
|
|
10
|
+
export type RateStrategyDecision = Readonly<{
|
|
11
|
+
nextRate: number;
|
|
12
|
+
shouldBackOff: boolean;
|
|
13
|
+
}>;
|
|
14
|
+
export type RateStrategy = (observation: RateStrategyObservation) => RateStrategyDecision;
|
|
15
|
+
export type AimdOptions = {
|
|
16
|
+
increaseBy?: number;
|
|
17
|
+
decreaseFactor?: number;
|
|
18
|
+
};
|
|
19
|
+
export type RateFailureOutcome = Readonly<{
|
|
20
|
+
kind: "returned-false";
|
|
21
|
+
}> | Readonly<{
|
|
22
|
+
kind: "thrown";
|
|
23
|
+
error: unknown;
|
|
24
|
+
}> | Readonly<{
|
|
25
|
+
kind: "rejected";
|
|
26
|
+
error: unknown;
|
|
27
|
+
}>;
|
|
28
|
+
export type RateOutcomeClassifier = (outcome: RateFailureOutcome) => boolean;
|
|
29
|
+
export type AdjustmentTiming = "interval" | "settled";
|
|
30
|
+
export declare const linear: RateStrategy;
|
|
31
|
+
export declare function aimd({ increaseBy, decreaseFactor }?: AimdOptions): RateStrategy;
|
|
2
32
|
/** Reports how one started callback settled: `undefined` for success, otherwise the failure. */
|
|
3
33
|
export type SettlementReporter = (outcome: RateFailureOutcome | undefined) => void;
|
|
4
34
|
export type AdaptiveRateOptions = {
|
|
5
|
-
|
|
6
|
-
|
|
35
|
+
minRate: number;
|
|
36
|
+
maxRate: number;
|
|
7
37
|
interval: number;
|
|
8
38
|
/** Makes one start due every `interval / rate` ms instead of `rate` starts once per interval. */
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
39
|
+
evenlySpaced: boolean;
|
|
40
|
+
errorThreshold: number;
|
|
41
|
+
backOff: boolean;
|
|
12
42
|
adjustmentTiming: AdjustmentTiming;
|
|
13
43
|
rateStrategy: RateStrategy;
|
|
14
44
|
rateOutcomeClassifier?: RateOutcomeClassifier;
|
|
@@ -40,6 +70,13 @@ export type AdaptiveRate = {
|
|
|
40
70
|
stop: () => void;
|
|
41
71
|
/** Stops and discards the observation. Settlements are ignored until `start()` or `stop()`. */
|
|
42
72
|
pause: () => void;
|
|
73
|
+
/**
|
|
74
|
+
* Makes no start due and no rate decision until `start()`, while settlements keep counting toward the
|
|
75
|
+
* next decision. Settled timing closes its collection interval early and decides that window once
|
|
76
|
+
* `start()` is called and every callback started in it has settled; interval timing begins a fresh
|
|
77
|
+
* interval on `start()`.
|
|
78
|
+
*/
|
|
79
|
+
suspend: () => void;
|
|
43
80
|
/** Reports a callback start. Call the returned reporter once when that callback settles. */
|
|
44
81
|
started: () => SettlementReporter;
|
|
45
82
|
};
|
package/dist/adaptive-rate.js
CHANGED
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
export const linear = ({ minRate, maxRate, currentRate, errorCount, errorThreshold, hasPendingWork, wasBackedOff, }) => {
|
|
2
|
+
if (errorCount >= errorThreshold) {
|
|
3
|
+
return { nextRate: Math.max(minRate, currentRate - 1), shouldBackOff: true };
|
|
4
|
+
}
|
|
5
|
+
if (!wasBackedOff && errorCount === 0 && hasPendingWork) {
|
|
6
|
+
return { nextRate: Math.min(maxRate, currentRate + 1), shouldBackOff: false };
|
|
7
|
+
}
|
|
8
|
+
return { nextRate: currentRate, shouldBackOff: false };
|
|
9
|
+
};
|
|
10
|
+
export function aimd({ increaseBy = 1, decreaseFactor = 0.5 } = {}) {
|
|
11
|
+
if (!Number.isInteger(increaseBy) || increaseBy < 1) {
|
|
12
|
+
throw new Error("increaseBy must be a positive integer");
|
|
13
|
+
}
|
|
14
|
+
if (!Number.isFinite(decreaseFactor) || decreaseFactor <= 0 || decreaseFactor >= 1) {
|
|
15
|
+
throw new Error("decreaseFactor must be a number greater than 0 and less than 1");
|
|
16
|
+
}
|
|
17
|
+
return ({ currentRate, errorCount, errorThreshold, hasPendingWork, wasBackedOff }) => {
|
|
18
|
+
if (errorCount >= errorThreshold) {
|
|
19
|
+
return { nextRate: Math.floor(currentRate * decreaseFactor), shouldBackOff: true };
|
|
20
|
+
}
|
|
21
|
+
if (!wasBackedOff && errorCount === 0 && hasPendingWork) {
|
|
22
|
+
return { nextRate: currentRate + increaseBy, shouldBackOff: false };
|
|
23
|
+
}
|
|
24
|
+
return { nextRate: currentRate, shouldBackOff: false };
|
|
25
|
+
};
|
|
26
|
+
}
|
|
1
27
|
const ignoreSettlement = () => { };
|
|
2
28
|
function validateDecision(decision) {
|
|
3
29
|
if (typeof decision !== "object" || decision === null) {
|
|
@@ -42,6 +68,7 @@ function intervalTiming(context) {
|
|
|
42
68
|
started: () => context.record,
|
|
43
69
|
drained: stop,
|
|
44
70
|
stop,
|
|
71
|
+
suspend: stop,
|
|
45
72
|
};
|
|
46
73
|
}
|
|
47
74
|
/** Decides once every callback that started during one collection interval has settled. */
|
|
@@ -51,6 +78,10 @@ function settledTiming(context) {
|
|
|
51
78
|
let collecting = false;
|
|
52
79
|
let starts = 0;
|
|
53
80
|
let outstanding = 0;
|
|
81
|
+
/** Collection has ended and the window's decision waits for its outstanding starts to settle. */
|
|
82
|
+
let closed = false;
|
|
83
|
+
/** Suspended: a closed window's decision waits for `start()` as well. */
|
|
84
|
+
let suspended = false;
|
|
54
85
|
function open() {
|
|
55
86
|
timer = undefined;
|
|
56
87
|
if (!context.hasPendingWork()) {
|
|
@@ -59,6 +90,7 @@ function settledTiming(context) {
|
|
|
59
90
|
}
|
|
60
91
|
window++;
|
|
61
92
|
collecting = true;
|
|
93
|
+
closed = false;
|
|
62
94
|
starts = 0;
|
|
63
95
|
outstanding = 0;
|
|
64
96
|
context.resume();
|
|
@@ -72,11 +104,13 @@ function settledTiming(context) {
|
|
|
72
104
|
return;
|
|
73
105
|
}
|
|
74
106
|
collecting = false;
|
|
107
|
+
closed = true;
|
|
75
108
|
context.hold();
|
|
76
109
|
if (outstanding === 0)
|
|
77
110
|
finish();
|
|
78
111
|
}
|
|
79
112
|
function finish() {
|
|
113
|
+
closed = false;
|
|
80
114
|
const current = window;
|
|
81
115
|
const hold = context.decide();
|
|
82
116
|
if (current !== window)
|
|
@@ -87,7 +121,16 @@ function settledTiming(context) {
|
|
|
87
121
|
open();
|
|
88
122
|
}
|
|
89
123
|
return {
|
|
90
|
-
start
|
|
124
|
+
start() {
|
|
125
|
+
suspended = false;
|
|
126
|
+
if (!closed) {
|
|
127
|
+
open();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
context.hold();
|
|
131
|
+
if (outstanding === 0)
|
|
132
|
+
finish();
|
|
133
|
+
},
|
|
91
134
|
started() {
|
|
92
135
|
if (!collecting)
|
|
93
136
|
return ignoreSettlement;
|
|
@@ -99,7 +142,7 @@ function settledTiming(context) {
|
|
|
99
142
|
return;
|
|
100
143
|
context.record(outcome);
|
|
101
144
|
outstanding--;
|
|
102
|
-
if (!
|
|
145
|
+
if (closed && !suspended && outstanding === 0)
|
|
103
146
|
finish();
|
|
104
147
|
};
|
|
105
148
|
},
|
|
@@ -108,14 +151,26 @@ function settledTiming(context) {
|
|
|
108
151
|
clearTimeout(timer);
|
|
109
152
|
timer = undefined;
|
|
110
153
|
collecting = false;
|
|
154
|
+
closed = false;
|
|
155
|
+
suspended = false;
|
|
111
156
|
window++;
|
|
112
157
|
context.idle();
|
|
113
158
|
},
|
|
159
|
+
suspend() {
|
|
160
|
+
clearTimeout(timer);
|
|
161
|
+
timer = undefined;
|
|
162
|
+
if (collecting) {
|
|
163
|
+
collecting = false;
|
|
164
|
+
closed = starts > 0;
|
|
165
|
+
}
|
|
166
|
+
suspended = true;
|
|
167
|
+
context.idle();
|
|
168
|
+
},
|
|
114
169
|
};
|
|
115
170
|
}
|
|
116
171
|
export function createAdaptiveRate(options, host) {
|
|
117
|
-
const {
|
|
118
|
-
let rate = Math.ceil((
|
|
172
|
+
const { minRate, maxRate, interval, evenlySpaced, errorThreshold, backOff, rateStrategy, rateOutcomeClassifier, onRateChange, } = options;
|
|
173
|
+
let rate = Math.ceil((maxRate + minRate) / 2);
|
|
119
174
|
let rateIncreases = 0;
|
|
120
175
|
let rateDecreases = 0;
|
|
121
176
|
let errorCount = 0;
|
|
@@ -147,10 +202,10 @@ export function createAdaptiveRate(options, host) {
|
|
|
147
202
|
function decide() {
|
|
148
203
|
const observation = Object.freeze({
|
|
149
204
|
currentRate: rate,
|
|
150
|
-
minRate
|
|
151
|
-
maxRate
|
|
205
|
+
minRate,
|
|
206
|
+
maxRate,
|
|
152
207
|
errorCount,
|
|
153
|
-
errorThreshold
|
|
208
|
+
errorThreshold,
|
|
154
209
|
hasPendingWork: host.hasPendingWork(),
|
|
155
210
|
wasBackedOff,
|
|
156
211
|
});
|
|
@@ -162,14 +217,14 @@ export function createAdaptiveRate(options, host) {
|
|
|
162
217
|
host.failed(error);
|
|
163
218
|
throw error;
|
|
164
219
|
}
|
|
165
|
-
const hold =
|
|
220
|
+
const hold = backOff && decision.shouldBackOff;
|
|
166
221
|
errorCount = 0;
|
|
167
222
|
wasBackedOff = hold;
|
|
168
|
-
applyRate(Math.min(
|
|
223
|
+
applyRate(Math.min(maxRate, Math.max(minRate, decision.nextRate)));
|
|
169
224
|
return hold;
|
|
170
225
|
}
|
|
171
226
|
function spacing() {
|
|
172
|
-
return
|
|
227
|
+
return evenlySpaced ? interval / rate : interval;
|
|
173
228
|
}
|
|
174
229
|
function scheduleStart(delay) {
|
|
175
230
|
clearTimeout(startTimer);
|
|
@@ -191,7 +246,7 @@ export function createAdaptiveRate(options, host) {
|
|
|
191
246
|
batchStarts = 0;
|
|
192
247
|
let slotFree;
|
|
193
248
|
try {
|
|
194
|
-
slotFree = host.startsDue(
|
|
249
|
+
slotFree = host.startsDue(evenlySpaced ? 1 : rate);
|
|
195
250
|
}
|
|
196
251
|
finally {
|
|
197
252
|
dispatching = false;
|
|
@@ -262,6 +317,10 @@ export function createAdaptiveRate(options, host) {
|
|
|
262
317
|
errorCount = 0;
|
|
263
318
|
ignoringSettlements = true;
|
|
264
319
|
},
|
|
320
|
+
suspend() {
|
|
321
|
+
ignoringSettlements = false;
|
|
322
|
+
timing.suspend();
|
|
323
|
+
},
|
|
265
324
|
started() {
|
|
266
325
|
if (dispatching && batchStarts++ === 0)
|
|
267
326
|
lastStart = Date.now();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adaptive-rate.js","sourceRoot":"","sources":["../src/adaptive-rate.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"adaptive-rate.js","sourceRoot":"","sources":["../src/adaptive-rate.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,41 +1,10 @@
|
|
|
1
|
+
import type { AdjustmentTiming, RateOutcomeClassifier, RateStrategy } from "./adaptive-rate.js";
|
|
1
2
|
import type { RetryBackoff, RetryClassifier } from "./retry-policy.js";
|
|
3
|
+
import type { ThrottleHandle } from "./scheduler.js";
|
|
4
|
+
export { aimd, linear } from "./adaptive-rate.js";
|
|
5
|
+
export type { AdjustmentTiming, AimdOptions, RateFailureOutcome, RateOutcomeClassifier, RateStrategy, RateStrategyDecision, RateStrategyObservation } from "./adaptive-rate.js";
|
|
2
6
|
export type { RetryBackoff, RetryClassifier } from "./retry-policy.js";
|
|
3
|
-
export type
|
|
4
|
-
currentRate: number;
|
|
5
|
-
minRate: number;
|
|
6
|
-
maxRate: number;
|
|
7
|
-
errorCount: number;
|
|
8
|
-
errorThreshold: number;
|
|
9
|
-
hasPendingWork: boolean;
|
|
10
|
-
wasBackedOff: boolean;
|
|
11
|
-
}>;
|
|
12
|
-
export type RateStrategyDecision = Readonly<{
|
|
13
|
-
nextRate: number;
|
|
14
|
-
shouldBackOff: boolean;
|
|
15
|
-
}>;
|
|
16
|
-
export type RateStrategy = (observation: RateStrategyObservation) => RateStrategyDecision;
|
|
17
|
-
export type AimdOptions = {
|
|
18
|
-
increaseBy?: number;
|
|
19
|
-
decreaseFactor?: number;
|
|
20
|
-
};
|
|
21
|
-
export type RateFailureOutcome = Readonly<{
|
|
22
|
-
kind: "returned-false";
|
|
23
|
-
}> | Readonly<{
|
|
24
|
-
kind: "thrown";
|
|
25
|
-
error: unknown;
|
|
26
|
-
}> | Readonly<{
|
|
27
|
-
kind: "rejected";
|
|
28
|
-
error: unknown;
|
|
29
|
-
}>;
|
|
30
|
-
export type RateOutcomeClassifier = (outcome: RateFailureOutcome) => boolean;
|
|
31
|
-
export type AdjustmentTiming = "interval" | "settled";
|
|
32
|
-
export declare const linear: RateStrategy;
|
|
33
|
-
export declare function aimd({ increaseBy, decreaseFactor }?: AimdOptions): RateStrategy;
|
|
34
|
-
export type ExecutionContext = Readonly<{
|
|
35
|
-
signal: AbortSignal;
|
|
36
|
-
}>;
|
|
37
|
-
/** Return `false` to signal failure (increments error count, triggers retry if configured). */
|
|
38
|
-
export type ThrottleCallback = (context: ExecutionContext) => boolean | void | Promise<boolean | void>;
|
|
7
|
+
export type { ExecutionContext, QueueLifecycleState, QueueState, TaskCallback, TaskHandle, ThrottleCallback, ThrottleFn, ThrottleHandle } from "./scheduler.js";
|
|
39
8
|
export type ThrottleOptions = {
|
|
40
9
|
min_rpi: number;
|
|
41
10
|
interval: number;
|
|
@@ -52,6 +21,8 @@ export type ThrottleOptions = {
|
|
|
52
21
|
concurrency?: number;
|
|
53
22
|
/** Maximum accepted callbacks that have not reached a terminal outcome. Omit for no limit. */
|
|
54
23
|
maxQueueSize?: number;
|
|
24
|
+
/** Longest cooldown, in ms, that `cooldownFor()` applies; longer requests are clamped. Default and maximum 2147483647. */
|
|
25
|
+
maxCooldown?: number;
|
|
55
26
|
/** Non-negative integer dead slots before queue compaction triggers. Default 512. */
|
|
56
27
|
compact_threshold?: number;
|
|
57
28
|
/** Policy used to request the next rate and any backoff after each observation window. */
|
|
@@ -64,27 +35,4 @@ export type ThrottleOptions = {
|
|
|
64
35
|
adjustmentTiming?: AdjustmentTiming;
|
|
65
36
|
onRateChange?: (rate: number) => void;
|
|
66
37
|
};
|
|
67
|
-
export type ThrottleFn = (callback: ThrottleCallback) => void;
|
|
68
|
-
export type QueueLifecycleState = "running" | "paused" | "stopped" | "aborted" | "failed";
|
|
69
|
-
export type QueueState = Readonly<{
|
|
70
|
-
rate: number;
|
|
71
|
-
pending: number;
|
|
72
|
-
active: number;
|
|
73
|
-
state: QueueLifecycleState;
|
|
74
|
-
started: number;
|
|
75
|
-
succeeded: number;
|
|
76
|
-
failed: number;
|
|
77
|
-
retried: number;
|
|
78
|
-
rateIncreases: number;
|
|
79
|
-
rateDecreases: number;
|
|
80
|
-
}>;
|
|
81
|
-
export type ThrottleHandle = ThrottleFn & {
|
|
82
|
-
pause: () => void;
|
|
83
|
-
resume: () => void;
|
|
84
|
-
stop: () => void;
|
|
85
|
-
abort: () => void;
|
|
86
|
-
waitForIdle: () => Promise<void>;
|
|
87
|
-
getState: () => QueueState;
|
|
88
|
-
readonly pending: number;
|
|
89
|
-
};
|
|
90
38
|
export declare function createThrottledQueue(options: ThrottleOptions): ThrottleHandle;
|
|
@@ -1,34 +1,13 @@
|
|
|
1
|
+
import { linear } from "./adaptive-rate.js";
|
|
1
2
|
import { createRetryPolicy } from "./retry-policy.js";
|
|
2
3
|
import { createScheduler } from "./scheduler.js";
|
|
4
|
+
// eslint-disable-next-line no-barrel-files/no-barrel-files -- The package entry exposes the rate strategies owned by the adaptive-rate module.
|
|
5
|
+
export { aimd, linear } from "./adaptive-rate.js";
|
|
3
6
|
const adjustmentTimings = new Set(["interval", "settled"]);
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
return { nextRate: Math.max(minRate, currentRate - 1), shouldBackOff: true };
|
|
7
|
-
}
|
|
8
|
-
if (!wasBackedOff && errorCount === 0 && hasPendingWork) {
|
|
9
|
-
return { nextRate: Math.min(maxRate, currentRate + 1), shouldBackOff: false };
|
|
10
|
-
}
|
|
11
|
-
return { nextRate: currentRate, shouldBackOff: false };
|
|
12
|
-
};
|
|
13
|
-
export function aimd({ increaseBy = 1, decreaseFactor = 0.5 } = {}) {
|
|
14
|
-
if (!Number.isInteger(increaseBy) || increaseBy < 1) {
|
|
15
|
-
throw new Error("increaseBy must be a positive integer");
|
|
16
|
-
}
|
|
17
|
-
if (!Number.isFinite(decreaseFactor) || decreaseFactor <= 0 || decreaseFactor >= 1) {
|
|
18
|
-
throw new Error("decreaseFactor must be a number greater than 0 and less than 1");
|
|
19
|
-
}
|
|
20
|
-
return ({ currentRate, errorCount, errorThreshold, hasPendingWork, wasBackedOff }) => {
|
|
21
|
-
if (errorCount >= errorThreshold) {
|
|
22
|
-
return { nextRate: Math.floor(currentRate * decreaseFactor), shouldBackOff: true };
|
|
23
|
-
}
|
|
24
|
-
if (!wasBackedOff && errorCount === 0 && hasPendingWork) {
|
|
25
|
-
return { nextRate: currentRate + increaseBy, shouldBackOff: false };
|
|
26
|
-
}
|
|
27
|
-
return { nextRate: currentRate, shouldBackOff: false };
|
|
28
|
-
};
|
|
29
|
-
}
|
|
7
|
+
/** The longest delay `setTimeout` honors; longer delays fire almost immediately. */
|
|
8
|
+
const maxTimerDelay = 2_147_483_647;
|
|
30
9
|
export function createThrottledQueue(options) {
|
|
31
|
-
const { min_rpi, interval, max_rpi = min_rpi, concurrency, maxQueueSize, compact_threshold = 512 } = options;
|
|
10
|
+
const { min_rpi, interval, max_rpi = min_rpi, concurrency, maxQueueSize, compact_threshold = 512, maxCooldown = maxTimerDelay } = options;
|
|
32
11
|
const errors_per_interval = options.errors_per_interval ?? 5;
|
|
33
12
|
if (!Number.isInteger(min_rpi) || min_rpi < 1) {
|
|
34
13
|
throw new Error("min_rpi must be a positive integer");
|
|
@@ -51,17 +30,25 @@ export function createThrottledQueue(options) {
|
|
|
51
30
|
if (!Number.isInteger(compact_threshold) || compact_threshold < 0) {
|
|
52
31
|
throw new Error("compact_threshold must be a non-negative integer");
|
|
53
32
|
}
|
|
33
|
+
if (!Number.isFinite(maxCooldown) || maxCooldown <= 0 || maxCooldown > maxTimerDelay) {
|
|
34
|
+
throw new Error(`maxCooldown must be a positive number no greater than ${maxTimerDelay}`);
|
|
35
|
+
}
|
|
54
36
|
if (options.adjustmentTiming !== undefined && !adjustmentTimings.has(options.adjustmentTiming)) {
|
|
55
37
|
throw new Error("adjustmentTiming must be either interval or settled");
|
|
56
38
|
}
|
|
57
39
|
const retryPolicy = createRetryPolicy(options);
|
|
58
|
-
return createScheduler(
|
|
59
|
-
|
|
60
|
-
|
|
40
|
+
return createScheduler({
|
|
41
|
+
concurrency: concurrency ?? Infinity,
|
|
42
|
+
capacity: maxQueueSize ?? Infinity,
|
|
43
|
+
compactThreshold: compact_threshold,
|
|
44
|
+
maxCooldown,
|
|
45
|
+
}, {
|
|
46
|
+
minRate: min_rpi,
|
|
47
|
+
maxRate: max_rpi,
|
|
61
48
|
interval,
|
|
62
|
-
|
|
63
|
-
errors_per_interval,
|
|
64
|
-
|
|
49
|
+
evenlySpaced: options.evenly_spaced ?? true,
|
|
50
|
+
errorThreshold: errors_per_interval,
|
|
51
|
+
backOff: options.back_off ?? false,
|
|
65
52
|
adjustmentTiming: options.adjustmentTiming ?? "interval",
|
|
66
53
|
rateStrategy: options.rateStrategy ?? linear,
|
|
67
54
|
rateOutcomeClassifier: options.rateOutcomeClassifier,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-throttled-queue.js","sourceRoot":"","sources":["../src/dynamic-throttled-queue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"dynamic-throttled-queue.js","sourceRoot":"","sources":["../src/dynamic-throttled-queue.ts"],"names":[],"mappings":"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"}
|
package/dist/pending-work.d.ts
CHANGED
|
@@ -29,6 +29,8 @@ export type PendingWork<T> = {
|
|
|
29
29
|
accept: (item: T) => void;
|
|
30
30
|
/** Makes the oldest queued item active and returns it, or `undefined` when nothing is queued. */
|
|
31
31
|
take: () => T | undefined;
|
|
32
|
+
/** Drops `item`, which must be queued or held, and releases its reservation. */
|
|
33
|
+
remove: (item: T) => void;
|
|
32
34
|
/**
|
|
33
35
|
* Ends one active item. `decide` runs once the item no longer counts as active but still holds its
|
|
34
36
|
* reservation; returning a retry keeps that reservation, returning nothing releases it. `observe`
|
package/dist/pending-work.js
CHANGED
|
@@ -2,6 +2,8 @@ export function createPendingWork(options, host) {
|
|
|
2
2
|
const { capacity, compactThreshold } = options;
|
|
3
3
|
const queue = [];
|
|
4
4
|
let head = 0;
|
|
5
|
+
/** Queued items that were removed but still sit between `head` and the tail; `take` skips them. */
|
|
6
|
+
const removed = new Set();
|
|
5
7
|
const held = [];
|
|
6
8
|
let frozen = false;
|
|
7
9
|
let active = 0;
|
|
@@ -9,7 +11,7 @@ export function createPendingWork(options, host) {
|
|
|
9
11
|
let deciding = 0;
|
|
10
12
|
const idleWaiters = [];
|
|
11
13
|
function queued() {
|
|
12
|
-
return queue.length - head;
|
|
14
|
+
return queue.length - head - removed.size;
|
|
13
15
|
}
|
|
14
16
|
function pending() {
|
|
15
17
|
return queued() + held.length;
|
|
@@ -44,6 +46,17 @@ export function createPendingWork(options, host) {
|
|
|
44
46
|
held.length = 0;
|
|
45
47
|
queue.length = 0;
|
|
46
48
|
head = 0;
|
|
49
|
+
removed.clear();
|
|
50
|
+
}
|
|
51
|
+
function compact() {
|
|
52
|
+
if (head === queue.length) {
|
|
53
|
+
queue.length = 0;
|
|
54
|
+
head = 0;
|
|
55
|
+
}
|
|
56
|
+
else if (head > compactThreshold && head > queue.length / 2) {
|
|
57
|
+
queue.splice(0, head);
|
|
58
|
+
head = 0;
|
|
59
|
+
}
|
|
47
60
|
}
|
|
48
61
|
return {
|
|
49
62
|
get queued() {
|
|
@@ -61,19 +74,24 @@ export function createPendingWork(options, host) {
|
|
|
61
74
|
queue.push(item);
|
|
62
75
|
},
|
|
63
76
|
take() {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
else if (head > compactThreshold && head > queue.length / 2) {
|
|
73
|
-
queue.splice(0, head);
|
|
74
|
-
head = 0;
|
|
77
|
+
while (head < queue.length) {
|
|
78
|
+
const item = queue[head++];
|
|
79
|
+
if (removed.delete(item))
|
|
80
|
+
continue;
|
|
81
|
+
active++;
|
|
82
|
+
compact();
|
|
83
|
+
return item;
|
|
75
84
|
}
|
|
76
|
-
|
|
85
|
+
compact();
|
|
86
|
+
return undefined;
|
|
87
|
+
},
|
|
88
|
+
remove(item) {
|
|
89
|
+
const index = held.findIndex(retry => retry.item === item);
|
|
90
|
+
if (index === -1)
|
|
91
|
+
removed.add(item);
|
|
92
|
+
else
|
|
93
|
+
clearTimeout(held.splice(index, 1)[0].timeout);
|
|
94
|
+
notifyIdle();
|
|
77
95
|
},
|
|
78
96
|
settle(decide, observe) {
|
|
79
97
|
active--;
|
package/dist/pending-work.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pending-work.js","sourceRoot":"","sources":["../src/pending-work.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pending-work.js","sourceRoot":"","sources":["../src/pending-work.ts"],"names":[],"mappings":"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"}
|
package/dist/retry-policy.d.ts
CHANGED
package/dist/scheduler.d.ts
CHANGED
|
@@ -1,4 +1,63 @@
|
|
|
1
1
|
import type { AdaptiveRateOptions } from "./adaptive-rate.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type { PendingWorkOptions } from "./pending-work.js";
|
|
3
3
|
import type { RetryPolicy } from "./retry-policy.js";
|
|
4
|
-
export
|
|
4
|
+
export type ExecutionContext = Readonly<{
|
|
5
|
+
signal: AbortSignal;
|
|
6
|
+
/** One-based attempt number; retries of the same item count up from 1. */
|
|
7
|
+
attempt: number;
|
|
8
|
+
}>;
|
|
9
|
+
/** Return `false` to signal failure (increments error count, triggers retry if configured). */
|
|
10
|
+
export type ThrottleCallback = (context: ExecutionContext) => boolean | void | Promise<boolean | void>;
|
|
11
|
+
export type ThrottleFn = (callback: ThrottleCallback) => void;
|
|
12
|
+
/** Submitted work. Every returned value, including `false`, is a result; only a throw or rejection fails. */
|
|
13
|
+
export type TaskCallback<T> = (context: ExecutionContext) => T | Promise<T>;
|
|
14
|
+
/** One accepted logical item across all of its attempts. */
|
|
15
|
+
export type TaskHandle<T> = Readonly<{
|
|
16
|
+
/** Settles once: the final value, the final failure, or the cancellation reason. */
|
|
17
|
+
result: Promise<T>;
|
|
18
|
+
/**
|
|
19
|
+
* Rejects `result` with `reason` (an `AbortError` by default) unless it already settled. A pending
|
|
20
|
+
* item is removed; an active one has its signal aborted and is never retried. Repeat calls do nothing.
|
|
21
|
+
*/
|
|
22
|
+
cancel: (reason?: unknown) => void;
|
|
23
|
+
}>;
|
|
24
|
+
export type QueueLifecycleState = "running" | "paused" | "stopped" | "aborted" | "failed";
|
|
25
|
+
export type QueueState = Readonly<{
|
|
26
|
+
rate: number;
|
|
27
|
+
pending: number;
|
|
28
|
+
active: number;
|
|
29
|
+
state: QueueLifecycleState;
|
|
30
|
+
started: number;
|
|
31
|
+
succeeded: number;
|
|
32
|
+
failed: number;
|
|
33
|
+
retried: number;
|
|
34
|
+
/** Submitted items whose result was rejected by `cancel()`. */
|
|
35
|
+
canceled: number;
|
|
36
|
+
rateIncreases: number;
|
|
37
|
+
rateDecreases: number;
|
|
38
|
+
/** Milliseconds, rounded up, until a server-directed cooldown ends; `0` when none is active. */
|
|
39
|
+
cooldownRemaining: number;
|
|
40
|
+
/** `cooldownFor()` calls that moved the cooldown deadline later. */
|
|
41
|
+
cooldowns: number;
|
|
42
|
+
/** Total milliseconds those calls added to the cooldown deadline. */
|
|
43
|
+
cooldownTotal: number;
|
|
44
|
+
}>;
|
|
45
|
+
export type ThrottleHandle = ThrottleFn & {
|
|
46
|
+
submit: <T>(callback: TaskCallback<T>) => TaskHandle<T>;
|
|
47
|
+
pause: () => void;
|
|
48
|
+
resume: () => void;
|
|
49
|
+
stop: () => void;
|
|
50
|
+
abort: () => void;
|
|
51
|
+
/** Starts nothing new for `delay` ms (capped at `maxCooldown`); never shortens an active cooldown. */
|
|
52
|
+
cooldownFor: (delay: number) => void;
|
|
53
|
+
waitForIdle: () => Promise<void>;
|
|
54
|
+
getState: () => QueueState;
|
|
55
|
+
readonly pending: number;
|
|
56
|
+
};
|
|
57
|
+
export type SchedulerOptions = PendingWorkOptions & {
|
|
58
|
+
/** Most callbacks awaiting asynchronous settlement at once. */
|
|
59
|
+
concurrency: number;
|
|
60
|
+
/** Longest cooldown, in ms, that `cooldownFor()` applies; longer requests are clamped to it. */
|
|
61
|
+
maxCooldown: number;
|
|
62
|
+
};
|
|
63
|
+
export declare function createScheduler(options: SchedulerOptions, adaptiveRateOptions: AdaptiveRateOptions, retryPolicy: RetryPolicy): ThrottleHandle;
|
package/dist/scheduler.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createAdaptiveRate } from "./adaptive-rate.js";
|
|
2
2
|
import { createPendingWork } from "./pending-work.js";
|
|
3
|
+
const returnedFalse = Object.freeze({ kind: "returned-false" });
|
|
3
4
|
/** The state each event leads to. An event missing from the current state's row is a no-op. */
|
|
4
5
|
const lifecycleTransitions = {
|
|
5
6
|
running: { pause: "paused", stop: "stopped", abort: "aborted", fail: "failed" },
|
|
@@ -9,26 +10,34 @@ const lifecycleTransitions = {
|
|
|
9
10
|
failed: {},
|
|
10
11
|
};
|
|
11
12
|
export function createScheduler(options, adaptiveRateOptions, retryPolicy) {
|
|
12
|
-
const { concurrency,
|
|
13
|
+
const { concurrency, maxCooldown, ...pendingWorkOptions } = options;
|
|
13
14
|
let lifecycle = { state: "running" };
|
|
14
15
|
let cnt_started = 0;
|
|
15
16
|
let cnt_succeeded = 0;
|
|
16
17
|
let cnt_failed = 0;
|
|
17
18
|
let cnt_retried = 0;
|
|
19
|
+
let cnt_canceled = 0;
|
|
20
|
+
let cnt_cooldowns = 0;
|
|
21
|
+
let cooldownTotal = 0;
|
|
22
|
+
/** Monotonic (`performance.now()`) time before which nothing starts; `undefined` when no cooldown is active. */
|
|
23
|
+
let cooldownDeadline;
|
|
24
|
+
/** Fires at the deadline. Cleared while stopped, which keeps the deadline for the restart. */
|
|
25
|
+
let cooldownTimer;
|
|
18
26
|
const abortController = new AbortController();
|
|
19
|
-
|
|
20
|
-
const
|
|
27
|
+
/** Submitted items whose result has not settled. */
|
|
28
|
+
const tasks = new Set();
|
|
29
|
+
const work = createPendingWork(pendingWorkOptions, { retryQueued: start });
|
|
21
30
|
const adaptiveRate = createAdaptiveRate(adaptiveRateOptions, {
|
|
22
31
|
hasPendingWork: () => work.queued > 0,
|
|
23
32
|
startsDue(limit) {
|
|
24
33
|
const batch = Math.min(limit, work.queued);
|
|
25
|
-
for (let started = 0; started < batch && work.active <
|
|
34
|
+
for (let started = 0; started < batch && work.active < concurrency && cooldownDeadline === undefined; started++) {
|
|
26
35
|
const item = work.take();
|
|
27
36
|
if (item === undefined)
|
|
28
37
|
break;
|
|
29
38
|
execute(item);
|
|
30
39
|
}
|
|
31
|
-
return work.active <
|
|
40
|
+
return work.active < concurrency;
|
|
32
41
|
},
|
|
33
42
|
failed(error) {
|
|
34
43
|
transition("fail", error);
|
|
@@ -43,87 +52,205 @@ export function createScheduler(options, adaptiveRateOptions, retryPolicy) {
|
|
|
43
52
|
switch (next) {
|
|
44
53
|
case "running":
|
|
45
54
|
work.thaw();
|
|
46
|
-
|
|
55
|
+
if (cooldownDeadline === undefined) {
|
|
56
|
+
adaptiveRate.start();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
armCooldown(cooldownDeadline);
|
|
60
|
+
adaptiveRate.suspend();
|
|
47
61
|
return;
|
|
48
62
|
case "paused":
|
|
63
|
+
// A cooldown keeps elapsing while paused: its deadline is the server's, not the queue's.
|
|
49
64
|
work.freeze();
|
|
50
65
|
adaptiveRate.pause();
|
|
51
66
|
return;
|
|
52
67
|
case "stopped":
|
|
68
|
+
clearTimeout(cooldownTimer);
|
|
69
|
+
cooldownTimer = undefined;
|
|
53
70
|
work.freeze();
|
|
54
71
|
adaptiveRate.stop();
|
|
55
72
|
return;
|
|
56
73
|
case "aborted":
|
|
74
|
+
clearCooldown();
|
|
57
75
|
adaptiveRate.stop();
|
|
58
76
|
work.discard();
|
|
59
77
|
abortController.abort();
|
|
78
|
+
rejectTasks(abortController.signal.reason);
|
|
60
79
|
return;
|
|
61
80
|
case "failed":
|
|
81
|
+
clearCooldown();
|
|
62
82
|
adaptiveRate.stop();
|
|
63
83
|
work.fail(error);
|
|
84
|
+
rejectTasks(error);
|
|
64
85
|
}
|
|
65
86
|
}
|
|
66
|
-
function
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const decision = retryPolicy.decide(outcome, item.attempt);
|
|
70
|
-
if (!decision)
|
|
71
|
-
return undefined;
|
|
72
|
-
cnt_retried++;
|
|
73
|
-
return { item: { fn: item.fn, attempt: item.attempt + 1 }, ...decision };
|
|
87
|
+
function armCooldown(deadline) {
|
|
88
|
+
clearTimeout(cooldownTimer);
|
|
89
|
+
cooldownTimer = setTimeout(expireCooldown, Math.max(0, deadline - performance.now()));
|
|
74
90
|
}
|
|
75
|
-
function
|
|
91
|
+
function expireCooldown() {
|
|
92
|
+
// A timer can fire slightly before the monotonic deadline; wait out the remainder.
|
|
93
|
+
if (cooldownDeadline !== undefined && performance.now() < cooldownDeadline) {
|
|
94
|
+
armCooldown(cooldownDeadline);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
cooldownTimer = undefined;
|
|
98
|
+
cooldownDeadline = undefined;
|
|
99
|
+
start();
|
|
100
|
+
}
|
|
101
|
+
function clearCooldown() {
|
|
102
|
+
clearTimeout(cooldownTimer);
|
|
103
|
+
cooldownTimer = undefined;
|
|
104
|
+
cooldownDeadline = undefined;
|
|
105
|
+
}
|
|
106
|
+
/** Rejects every unsettled submitted item with `reason` and aborts its signal. */
|
|
107
|
+
function rejectTasks(reason) {
|
|
108
|
+
// Snapshot first: abort listeners run synchronously and may re-enter the queue.
|
|
109
|
+
const affected = [...tasks];
|
|
110
|
+
tasks.clear();
|
|
111
|
+
for (const task of affected) {
|
|
112
|
+
task.controller.abort(reason);
|
|
113
|
+
task.reject(reason);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function nextAttempt(item, outcome, value) {
|
|
117
|
+
const { task } = item;
|
|
118
|
+
if (outcome) {
|
|
119
|
+
const decision = retryPolicy.decide(outcome, item.attempt);
|
|
120
|
+
if (decision && (task === undefined || tasks.has(task))) {
|
|
121
|
+
cnt_retried++;
|
|
122
|
+
item.attempt++;
|
|
123
|
+
if (task)
|
|
124
|
+
task.active = false;
|
|
125
|
+
return { item, ...decision };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (task && tasks.delete(task)) {
|
|
129
|
+
// Submitted items never return-false, so a failure always carries its error.
|
|
130
|
+
if (outcome)
|
|
131
|
+
task.reject("error" in outcome ? outcome.error : undefined);
|
|
132
|
+
else
|
|
133
|
+
task.resolve(value);
|
|
134
|
+
}
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
function handleSettlement(item, outcome, value, reportSettlement, resume = false) {
|
|
76
138
|
if (lifecycle.state === "aborted" || lifecycle.state === "failed") {
|
|
77
139
|
work.settle();
|
|
78
140
|
return;
|
|
79
141
|
}
|
|
80
|
-
|
|
81
|
-
cnt_failed++;
|
|
82
|
-
else
|
|
83
|
-
cnt_succeeded++;
|
|
84
|
-
work.settle(() => nextAttempt(item, outcome), () => {
|
|
142
|
+
const observe = () => {
|
|
85
143
|
reportSettlement(outcome);
|
|
86
144
|
// An async settlement frees a slot; a start may be due if concurrency held one back.
|
|
87
145
|
if (resume)
|
|
88
146
|
start();
|
|
89
|
-
}
|
|
147
|
+
};
|
|
148
|
+
if (item.task && !tasks.has(item.task)) {
|
|
149
|
+
// Canceled: the attempt's outcome neither counts nor retries, but the observation window still closes.
|
|
150
|
+
outcome = undefined;
|
|
151
|
+
work.settle(undefined, observe);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (outcome)
|
|
155
|
+
cnt_failed++;
|
|
156
|
+
else
|
|
157
|
+
cnt_succeeded++;
|
|
158
|
+
work.settle(() => nextAttempt(item, outcome, value), observe);
|
|
159
|
+
}
|
|
160
|
+
/** Only fire-and-forget callbacks report failure by returning `false`. */
|
|
161
|
+
function returnedOutcome(item, value) {
|
|
162
|
+
return value === false && item.task === undefined ? returnedFalse : undefined;
|
|
90
163
|
}
|
|
91
164
|
function execute(item) {
|
|
92
165
|
const reportSettlement = adaptiveRate.started();
|
|
93
166
|
cnt_started++;
|
|
167
|
+
const { task } = item;
|
|
168
|
+
if (task)
|
|
169
|
+
task.active = true;
|
|
94
170
|
let result;
|
|
95
171
|
try {
|
|
96
|
-
result = item.fn({ signal: abortController.signal });
|
|
172
|
+
result = item.fn({ signal: task?.controller.signal ?? abortController.signal, attempt: item.attempt });
|
|
97
173
|
}
|
|
98
174
|
catch (error) {
|
|
99
|
-
handleSettlement(item, { kind: "thrown", error }, reportSettlement);
|
|
175
|
+
handleSettlement(item, { kind: "thrown", error }, undefined, reportSettlement);
|
|
100
176
|
return;
|
|
101
177
|
}
|
|
102
178
|
if (result instanceof Promise) {
|
|
103
|
-
void result.then(value => handleSettlement(item, value
|
|
179
|
+
void result.then((value) => handleSettlement(item, returnedOutcome(item, value), value, reportSettlement, true), (error) => handleSettlement(item, { kind: "rejected", error }, undefined, reportSettlement, true));
|
|
104
180
|
return;
|
|
105
181
|
}
|
|
106
|
-
handleSettlement(item, result
|
|
182
|
+
handleSettlement(item, returnedOutcome(item, result), result, reportSettlement);
|
|
107
183
|
}
|
|
108
184
|
function start() {
|
|
109
|
-
if (lifecycle.state === "running")
|
|
185
|
+
if (lifecycle.state === "running" && cooldownDeadline === undefined)
|
|
110
186
|
adaptiveRate.start();
|
|
111
187
|
}
|
|
112
|
-
|
|
188
|
+
/** Admits `item` or throws: the queue is terminal or every capacity reservation is taken. */
|
|
189
|
+
function accept(item) {
|
|
113
190
|
if (lifecycle.state === "aborted")
|
|
114
191
|
throw new Error("Cannot enqueue work after the queue has been aborted");
|
|
115
192
|
if (lifecycle.state === "failed")
|
|
116
193
|
throw lifecycle.error;
|
|
117
|
-
work.accept(
|
|
194
|
+
work.accept(item);
|
|
195
|
+
}
|
|
196
|
+
/** Schedules newly accepted work. */
|
|
197
|
+
function schedule() {
|
|
118
198
|
if (lifecycle.state === "stopped")
|
|
119
199
|
transition("restart");
|
|
120
200
|
else
|
|
121
201
|
start();
|
|
122
202
|
}
|
|
203
|
+
function enqueue(callback) {
|
|
204
|
+
accept({ fn: callback, attempt: 1 });
|
|
205
|
+
schedule();
|
|
206
|
+
}
|
|
207
|
+
enqueue.submit = (callback) => {
|
|
208
|
+
const { promise, resolve, reject } = Promise.withResolvers();
|
|
209
|
+
const task = { controller: new AbortController(), resolve: resolve, reject, active: false };
|
|
210
|
+
const item = { fn: callback, attempt: 1, task };
|
|
211
|
+
accept(item);
|
|
212
|
+
tasks.add(task);
|
|
213
|
+
schedule();
|
|
214
|
+
return {
|
|
215
|
+
result: promise,
|
|
216
|
+
cancel(reason) {
|
|
217
|
+
if (!tasks.delete(task))
|
|
218
|
+
return;
|
|
219
|
+
cnt_canceled++;
|
|
220
|
+
if (!task.active)
|
|
221
|
+
work.remove(item);
|
|
222
|
+
task.controller.abort(reason);
|
|
223
|
+
task.reject(task.controller.signal.reason);
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
};
|
|
123
227
|
enqueue.pause = () => transition("pause");
|
|
124
228
|
enqueue.resume = () => transition("resume");
|
|
125
229
|
enqueue.stop = () => transition("stop");
|
|
126
230
|
enqueue.abort = () => transition("abort");
|
|
231
|
+
enqueue.cooldownFor = (delay) => {
|
|
232
|
+
if (!Number.isFinite(delay) || delay < 0)
|
|
233
|
+
throw new RangeError("cooldownFor delay must be a finite non-negative number");
|
|
234
|
+
if (lifecycle.state === "aborted" || lifecycle.state === "failed")
|
|
235
|
+
return;
|
|
236
|
+
const now = performance.now();
|
|
237
|
+
const latest = Math.max(now, cooldownDeadline ?? now);
|
|
238
|
+
const deadline = now + Math.min(delay, maxCooldown);
|
|
239
|
+
// Hints are absolute ("not before now + delay"), so overlapping ones keep the later deadline, never add up.
|
|
240
|
+
if (deadline <= latest)
|
|
241
|
+
return;
|
|
242
|
+
const beginning = cooldownDeadline === undefined;
|
|
243
|
+
cnt_cooldowns++;
|
|
244
|
+
cooldownTotal += deadline - latest;
|
|
245
|
+
cooldownDeadline = deadline;
|
|
246
|
+
// Stopped: the restart arms the timer for whatever remains.
|
|
247
|
+
if (lifecycle.state === "stopped")
|
|
248
|
+
return;
|
|
249
|
+
armCooldown(deadline);
|
|
250
|
+
// Paused: adaptive rate is already stopped; `resume()` suspends it instead of starting it.
|
|
251
|
+
if (beginning && lifecycle.state === "running")
|
|
252
|
+
adaptiveRate.suspend();
|
|
253
|
+
};
|
|
127
254
|
enqueue.waitForIdle = async () => {
|
|
128
255
|
if (lifecycle.state === "failed")
|
|
129
256
|
throw lifecycle.error;
|
|
@@ -138,8 +265,12 @@ export function createScheduler(options, adaptiveRateOptions, retryPolicy) {
|
|
|
138
265
|
succeeded: cnt_succeeded,
|
|
139
266
|
failed: cnt_failed,
|
|
140
267
|
retried: cnt_retried,
|
|
268
|
+
canceled: cnt_canceled,
|
|
141
269
|
rateIncreases: adaptiveRate.rateIncreases,
|
|
142
270
|
rateDecreases: adaptiveRate.rateDecreases,
|
|
271
|
+
cooldownRemaining: cooldownDeadline === undefined ? 0 : Math.max(0, Math.ceil(cooldownDeadline - performance.now())),
|
|
272
|
+
cooldowns: cnt_cooldowns,
|
|
273
|
+
cooldownTotal,
|
|
143
274
|
});
|
|
144
275
|
Object.defineProperty(enqueue, "pending", { get: () => work.pending });
|
|
145
276
|
return enqueue;
|
package/dist/scheduler.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,2BAA2B;
|
|
1
|
+
{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,2BAA2B;AAExD,OAAO,EAAE,iBAAiB,EAAE,0BAA0B;AA6EtD,MAAM,aAAa,GAAuB,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,CAAC;AAOpF,+FAA+F;AAC/F,MAAM,oBAAoB,GAA0G;IAClI,OAAO,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE;IAC/E,MAAM,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE;IAChE,OAAO,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE;IACjD,OAAO,EAAE,EAAE;IACX,MAAM,EAAE,EAAE;CACX,CAAC;AASF,MAAM,UAAU,eAAe,CAAC,OAAyB,EAAE,mBAAwC,EAAE,WAAwB;IAC3H,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,GAAG,kBAAkB,EAAE,GAAG,OAAO,CAAC;IACpE,IAAI,SAAS,GAAc,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IAChD,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,IAAI,aAAa,GAAG,CAAC,CAAC;IACtB,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,aAAa,GAAG,CAAC,CAAC;IACtB,IAAI,aAAa,GAAG,CAAC,CAAC;IACtB,gHAAgH;IAChH,IAAI,gBAAoC,CAAC;IACzC,8FAA8F;IAC9F,IAAI,aAAwD,CAAC;IAC7D,MAAM,eAAe,GAAG,IAAI,eAAe,EAAE,CAAC;IAC9C,oDAAoD;IACpD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAQ,CAAC;IAC9B,MAAM,IAAI,GAAG,iBAAiB,CAAY,kBAAkB,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC;IACtF,MAAM,YAAY,GAAG,kBAAkB,CAAC,mBAAmB,EAAE;QAC3D,cAAc,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;QACrC,SAAS,CAAC,KAAK;YACb,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;YAC3C,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,KAAK,IAAI,IAAI,CAAC,MAAM,GAAG,WAAW,IAAI,gBAAgB,KAAK,SAAS,EAAE,OAAO,EAAE,EAAE,CAAC;gBAChH,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;gBACzB,IAAI,IAAI,KAAK,SAAS;oBAAE,MAAM;gBAC9B,OAAO,CAAC,IAAI,CAAC,CAAC;YAChB,CAAC;YACD,OAAO,IAAI,CAAC,MAAM,GAAG,WAAW,CAAC;QACnC,CAAC;QACD,MAAM,CAAC,KAAK;YACV,UAAU,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAC5B,CAAC;KACF,CAAC,CAAC;IAEH,iGAAiG;IACjG,SAAS,UAAU,CAAC,KAAqB,EAAE,KAAe;QACxD,MAAM,IAAI,GAAG,oBAAoB,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC;QAC1D,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO;QAC/B,SAAS,GAAG,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QACzE,QAAQ,IAAI,EAAE,CAAC;YACb,KAAK,SAAS;gBACZ,IAAI,CAAC,IAAI,EAAE,CAAC;gBACZ,IAAI,gBAAgB,KAAK,SAAS,EAAE,CAAC;oBACnC,YAAY,CAAC,KAAK,EAAE,CAAC;oBACrB,OAAO;gBACT,CAAC;gBACD,WAAW,CAAC,gBAAgB,CAAC,CAAC;gBAC9B,YAAY,CAAC,OAAO,EAAE,CAAC;gBACvB,OAAO;YACT,KAAK,QAAQ;gBACX,yFAAyF;gBACzF,IAAI,CAAC,MAAM,EAAE,CAAC;gBACd,YAAY,CAAC,KAAK,EAAE,CAAC;gBACrB,OAAO;YACT,KAAK,SAAS;gBACZ,YAAY,CAAC,aAAa,CAAC,CAAC;gBAC5B,aAAa,GAAG,SAAS,CAAC;gBAC1B,IAAI,CAAC,MAAM,EAAE,CAAC;gBACd,YAAY,CAAC,IAAI,EAAE,CAAC;gBACpB,OAAO;YACT,KAAK,SAAS;gBACZ,aAAa,EAAE,CAAC;gBAChB,YAAY,CAAC,IAAI,EAAE,CAAC;gBACpB,IAAI,CAAC,OAAO,EAAE,CAAC;gBACf,eAAe,CAAC,KAAK,EAAE,CAAC;gBACxB,WAAW,CAAC,eAAe,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBAC3C,OAAO;YACT,KAAK,QAAQ;gBACX,aAAa,EAAE,CAAC;gBAChB,YAAY,CAAC,IAAI,EAAE,CAAC;gBACpB,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACjB,WAAW,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IAED,SAAS,WAAW,CAAC,QAAgB;QACnC,YAAY,CAAC,aAAa,CAAC,CAAC;QAC5B,aAAa,GAAG,UAAU,CAAC,cAAc,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IACxF,CAAC;IAED,SAAS,cAAc;QACrB,mFAAmF;QACnF,IAAI,gBAAgB,KAAK,SAAS,IAAI,WAAW,CAAC,GAAG,EAAE,GAAG,gBAAgB,EAAE,CAAC;YAC3E,WAAW,CAAC,gBAAgB,CAAC,CAAC;YAC9B,OAAO;QACT,CAAC;QACD,aAAa,GAAG,SAAS,CAAC;QAC1B,gBAAgB,GAAG,SAAS,CAAC;QAC7B,KAAK,EAAE,CAAC;IACV,CAAC;IAED,SAAS,aAAa;QACpB,YAAY,CAAC,aAAa,CAAC,CAAC;QAC5B,aAAa,GAAG,SAAS,CAAC;QAC1B,gBAAgB,GAAG,SAAS,CAAC;IAC/B,CAAC;IAED,kFAAkF;IAClF,SAAS,WAAW,CAAC,MAAe;QAClC,gFAAgF;QAChF,MAAM,QAAQ,GAAG,CAAE,GAAG,KAAK,CAAE,CAAC;QAC9B,KAAK,CAAC,KAAK,EAAE,CAAC;QACd,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;YAC5B,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAC9B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;IAED,SAAS,WAAW,CAAC,IAAe,EAAE,OAAuC,EAAE,KAAc;QAC3F,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC;QACtB,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,QAAQ,GAAG,WAAW,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;YAC3D,IAAI,QAAQ,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;gBACxD,WAAW,EAAE,CAAC;gBACd,IAAI,CAAC,OAAO,EAAE,CAAC;gBACf,IAAI,IAAI;oBAAE,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;gBAC9B,OAAO,EAAE,IAAI,EAAE,GAAG,QAAQ,EAAE,CAAC;YAC/B,CAAC;QACH,CAAC;QACD,IAAI,IAAI,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/B,6EAA6E;YAC7E,IAAI,OAAO;gBAAE,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;;gBACpE,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC3B,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,SAAS,gBAAgB,CAAC,IAAe,EAAE,OAAuC,EAAE,KAAc,EAAE,gBAAoC,EAAE,MAAM,GAAG,KAAK;QACtJ,IAAI,SAAS,CAAC,KAAK,KAAK,SAAS,IAAI,SAAS,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;YAClE,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,OAAO;QACT,CAAC;QACD,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,gBAAgB,CAAC,OAAO,CAAC,CAAC;YAC1B,qFAAqF;YACrF,IAAI,MAAM;gBAAE,KAAK,EAAE,CAAC;QACtB,CAAC,CAAC;QACF,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACvC,uGAAuG;YACvG,OAAO,GAAG,SAAS,CAAC;YACpB,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;YAChC,OAAO;QACT,CAAC;QACD,IAAI,OAAO;YAAE,UAAU,EAAE,CAAC;;YACrB,aAAa,EAAE,CAAC;QACrB,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC;IAChE,CAAC;IAED,0EAA0E;IAC1E,SAAS,eAAe,CAAC,IAAe,EAAE,KAAc;QACtD,OAAO,KAAK,KAAK,KAAK,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC;IAChF,CAAC;IAED,SAAS,OAAO,CAAC,IAAe;QAC9B,MAAM,gBAAgB,GAAG,YAAY,CAAC,OAAO,EAAE,CAAC;QAChD,WAAW,EAAE,CAAC;QACd,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC;QACtB,IAAI,IAAI;YAAE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QAC7B,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,MAAM,IAAI,eAAe,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;QACzG,CAAC;QACD,OAAO,KAAK,EAAE,CAAC;YACb,gBAAgB,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,gBAAgB,CAAC,CAAC;YAC/E,OAAO;QACT,CAAC;QACD,IAAI,MAAM,YAAY,OAAO,EAAE,CAAC;YAC9B,KAAK,MAAM,CAAC,IAAI,CACd,CAAC,KAAc,EAAE,EAAE,CAAC,gBAAgB,CAAC,IAAI,EAAE,eAAe,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,IAAI,CAAC,EACvG,CAAC,KAAc,EAAE,EAAE,CAAC,gBAAgB,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,gBAAgB,EAAE,IAAI,CAAC,CAC3G,CAAC;YACF,OAAO;QACT,CAAC;QACD,gBAAgB,CAAC,IAAI,EAAE,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAClF,CAAC;IAED,SAAS,KAAK;QACZ,IAAI,SAAS,CAAC,KAAK,KAAK,SAAS,IAAI,gBAAgB,KAAK,SAAS;YAAE,YAAY,CAAC,KAAK,EAAE,CAAC;IAC5F,CAAC;IAED,6FAA6F;IAC7F,SAAS,MAAM,CAAC,IAAe;QAC7B,IAAI,SAAS,CAAC,KAAK,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;QAC3G,IAAI,SAAS,CAAC,KAAK,KAAK,QAAQ;YAAE,MAAM,SAAS,CAAC,KAAK,CAAC;QACxD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACpB,CAAC;IAED,qCAAqC;IACrC,SAAS,QAAQ;QACf,IAAI,SAAS,CAAC,KAAK,KAAK,SAAS;YAAE,UAAU,CAAC,SAAS,CAAC,CAAC;;YACpD,KAAK,EAAE,CAAC;IACf,CAAC;IAED,SAAS,OAAO,CAAC,QAA0B;QACzC,MAAM,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;QACrC,QAAQ,EAAE,CAAC;IACb,CAAC;IAED,OAAO,CAAC,MAAM,GAAG,CAAI,QAAyB,EAAiB,EAAE;QAC/D,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,aAAa,EAAK,CAAC;QAChE,MAAM,IAAI,GAAS,EAAE,UAAU,EAAE,IAAI,eAAe,EAAE,EAAE,OAAO,EAAE,OAAmC,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;QAC9H,MAAM,IAAI,GAAc,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC;QAC3D,MAAM,CAAC,IAAI,CAAC,CAAC;QACb,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAChB,QAAQ,EAAE,CAAC;QACX,OAAO;YACL,MAAM,EAAE,OAAO;YACf,MAAM,CAAC,MAAgB;gBACrB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC;oBAAE,OAAO;gBAChC,YAAY,EAAE,CAAC;gBACf,IAAI,CAAC,IAAI,CAAC,MAAM;oBAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;gBACpC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;gBAC9B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAC7C,CAAC;SACF,CAAC;IACJ,CAAC,CAAC;IAEF,OAAO,CAAC,KAAK,GAAG,GAAG,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IAC1C,OAAO,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC5C,OAAO,CAAC,IAAI,GAAG,GAAG,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IACxC,OAAO,CAAC,KAAK,GAAG,GAAG,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IAC1C,OAAO,CAAC,WAAW,GAAG,CAAC,KAAa,EAAE,EAAE;QACtC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC;YAAE,MAAM,IAAI,UAAU,CAAC,wDAAwD,CAAC,CAAC;QACzH,IAAI,SAAS,CAAC,KAAK,KAAK,SAAS,IAAI,SAAS,CAAC,KAAK,KAAK,QAAQ;YAAE,OAAO;QAC1E,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,gBAAgB,IAAI,GAAG,CAAC,CAAC;QACtD,MAAM,QAAQ,GAAG,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;QACpD,4GAA4G;QAC5G,IAAI,QAAQ,IAAI,MAAM;YAAE,OAAO;QAC/B,MAAM,SAAS,GAAG,gBAAgB,KAAK,SAAS,CAAC;QACjD,aAAa,EAAE,CAAC;QAChB,aAAa,IAAI,QAAQ,GAAG,MAAM,CAAC;QACnC,gBAAgB,GAAG,QAAQ,CAAC;QAC5B,4DAA4D;QAC5D,IAAI,SAAS,CAAC,KAAK,KAAK,SAAS;YAAE,OAAO;QAC1C,WAAW,CAAC,QAAQ,CAAC,CAAC;QACtB,2FAA2F;QAC3F,IAAI,SAAS,IAAI,SAAS,CAAC,KAAK,KAAK,SAAS;YAAE,YAAY,CAAC,OAAO,EAAE,CAAC;IACzE,CAAC,CAAC;IACF,OAAO,CAAC,WAAW,GAAG,KAAK,IAAI,EAAE;QAC/B,IAAI,SAAS,CAAC,KAAK,KAAK,QAAQ;YAAE,MAAM,SAAS,CAAC,KAAK,CAAC;QACxD,OAAO,IAAI,CAAC,QAAQ,EAAE,CAAC;IACzB,CAAC,CAAC;IACF,OAAO,CAAC,QAAQ,GAAG,GAAe,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC;QACjD,IAAI,EAAE,YAAY,CAAC,IAAI;QACvB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,KAAK,EAAE,SAAS,CAAC,KAAK;QACtB,OAAO,EAAE,WAAW;QACpB,SAAS,EAAE,aAAa;QACxB,MAAM,EAAE,UAAU;QAClB,OAAO,EAAE,WAAW;QACpB,QAAQ,EAAE,YAAY;QACtB,aAAa,EAAE,YAAY,CAAC,aAAa;QACzC,aAAa,EAAE,YAAY,CAAC,aAAa;QACzC,iBAAiB,EAAE,gBAAgB,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,gBAAgB,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,CAAC;QACpH,SAAS,EAAE,aAAa;QACxB,aAAa;KACd,CAAC,CAAC;IACH,MAAM,CAAC,cAAc,CAAC,OAAO,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;IACvE,OAAO,OAAyB,CAAC;AACnC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dynamic-throttled-queue",
|
|
3
|
-
"version": "2.3.0-rc.
|
|
3
|
+
"version": "2.3.0-rc.78d8bef",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Dynamically throttles arbitrary code to execute between a minimum and maximum number of times per interval. Best for making throttled API requests.",
|
|
6
6
|
"files": [
|