dynamic-throttled-queue 3.0.0-rc.1d87d99 → 3.0.0-rc.6e88a00
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 +7 -1
- package/dist/adaptive-rate.d.ts +2 -1
- package/dist/adaptive-rate.js +7 -2
- package/dist/adaptive-rate.js.map +1 -1
- package/dist/scheduler.js +33 -17
- package/dist/scheduler.js.map +1 -1
- package/package.json +8 -6
- package/skills/configure-throttled-queue/SKILL.md +343 -0
- package/skills/configure-throttled-queue/references/lifecycle.md +46 -0
- package/skills/configure-throttled-queue/references/rate-strategies.md +68 -0
- package/skills/configure-throttled-queue/references/retries.md +52 -0
- package/skills/configure-throttled-queue/references/state-and-idle.md +38 -0
package/README.md
CHANGED
|
@@ -437,7 +437,7 @@ Strategies must return a finite integer `nextRate` and a boolean `shouldBackOff`
|
|
|
437
437
|
|
|
438
438
|
`cooldownFor(delay)` stops the queue from starting anything new for `delay` milliseconds. See [Honour `Retry-After` on HTTP 429](#honour-retry-after-on-http-429) for a full example.
|
|
439
439
|
|
|
440
|
-
- **Scheduling.** Pending work, newly queued work, and retries are kept and do not start. Active callbacks keep running and settle normally. When the cooldown ends, starts resume without new work being queued; the first one is due one spacing later, as after any fresh start. Delayed retries keep counting down and join the queue when due.
|
|
440
|
+
- **Scheduling.** Pending work, newly queued work, and retries are kept and do not start. Active callbacks keep running and settle normally. When the cooldown ends, starts resume without new work being queued; the first one is due one spacing later, as after any fresh start. Delayed retries keep counting down and join the queue when due. While nothing is queued, the cooldown holds no timer, so it does not keep a Node.js process alive after `waitForIdle()` resolves.
|
|
441
441
|
- **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.
|
|
442
442
|
- **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.
|
|
443
443
|
- **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 work that restarts the queue still waits for it. `abort()` and a strategy failure clear the cooldown; later valid calls do nothing.
|
|
@@ -450,6 +450,12 @@ Strategies must return a finite integer `nextRate` and a boolean `shouldBackOff`
|
|
|
450
450
|
|
|
451
451
|
- The `pending` property is removed. Read `getState().pending` instead.
|
|
452
452
|
- The `RateFailureOutcome` type is renamed to `FailureOutcome`.
|
|
453
|
+
- `QueueLifecycleState` gains `"failed"`. An exhaustive `switch` on `getState().state` needs a case for it.
|
|
454
|
+
- When `rateStrategy` throws or returns a malformed decision, the queue now reports `state: "failed"`, discards pending work, rejects `waitForIdle()` with the strategy error, and throws that error from later enqueues. In v2 it kept reporting `"running"`, kept the pending work, and `waitForIdle()` never settled.
|
|
455
|
+
- An unknown `retryBackoff.strategy` now throws `retryBackoff.strategy must be fixed, linear, or exponential` at creation. v2 silently used the fixed delay.
|
|
456
|
+
- Settled timing now matches the documented contract: the window collected right after a backoff holds the rate steady, an empty collection interval makes no rate decision, and a backoff that ends with nothing queued no longer stops later callbacks from starting.
|
|
457
|
+
- Exported types gained required members: `ExecutionContext.attempt`; `QueueState.canceled`, `cooldownRemaining`, `cooldowns` and `cooldownTotal`; `ThrottleHandle.submit` and `cooldownFor`. Code that reads them is unaffected. Hand-built contexts, state objects and handle test doubles need the new members.
|
|
458
|
+
- `ThrottleCallback` now returns `unknown`, so any callback type-checks. Code that calls a `ThrottleCallback` itself gets `unknown` back instead of `boolean | void | Promise<boolean | void>`.
|
|
453
459
|
|
|
454
460
|
### From v1
|
|
455
461
|
|
package/dist/adaptive-rate.d.ts
CHANGED
|
@@ -63,7 +63,8 @@ export type AdaptiveRate = {
|
|
|
63
63
|
/**
|
|
64
64
|
* Ends a pause, then begins observing if idle and work is pending. While starts are open and no start
|
|
65
65
|
* is scheduled (concurrency was full), makes starts due now once a spacing has passed since the last
|
|
66
|
-
* start, or schedules them for when it has
|
|
66
|
+
* start, or schedules them for when it has; with nothing pending then, drains instead. During
|
|
67
|
+
* `startsDue`, the batch's own follow-up covers it.
|
|
67
68
|
*/
|
|
68
69
|
start: () => void;
|
|
69
70
|
/** Ends observation. Settled timing discards its in-progress window. */
|
package/dist/adaptive-rate.js
CHANGED
|
@@ -301,11 +301,16 @@ export function createAdaptiveRate(options, host) {
|
|
|
301
301
|
},
|
|
302
302
|
start() {
|
|
303
303
|
ignoringSettlements = false;
|
|
304
|
-
|
|
304
|
+
const waitingForSlot = pacing === "open" && startTimer === undefined && !dispatching;
|
|
305
|
+
if (!host.hasPendingWork()) {
|
|
306
|
+
// Work removed while every slot was busy (a cancel) never reaches `startDue`, which drains.
|
|
307
|
+
if (waitingForSlot)
|
|
308
|
+
timing.drained();
|
|
305
309
|
return;
|
|
310
|
+
}
|
|
306
311
|
if (pacing === "idle")
|
|
307
312
|
timing.start();
|
|
308
|
-
else if (
|
|
313
|
+
else if (waitingForSlot)
|
|
309
314
|
startDue();
|
|
310
315
|
},
|
|
311
316
|
stop() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adaptive-rate.js","sourceRoot":"","sources":["../src/adaptive-rate.ts"],"names":[],"mappings":"AA+BA,MAAM,CAAC,MAAM,MAAM,GAAiB,CAAC,EACnC,OAAO,EACP,OAAO,EACP,WAAW,EACX,UAAU,EACV,cAAc,EACd,cAAc,EACd,YAAY,GACb,EAAE,EAAE;IACH,IAAI,UAAU,IAAI,cAAc,EAAE,CAAC;QACjC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,WAAW,GAAG,CAAC,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;IAC/E,CAAC;IACD,IAAI,CAAC,YAAY,IAAI,UAAU,KAAK,CAAC,IAAI,cAAc,EAAE,CAAC;QACxD,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,WAAW,GAAG,CAAC,CAAC,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;IAChF,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;AACzD,CAAC,CAAC;AAEF,MAAM,UAAU,IAAI,CAAC,EAAE,UAAU,GAAG,CAAC,EAAE,cAAc,GAAG,GAAG,KAAkB,EAAE;IAC7E,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,UAAU,GAAG,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAC3D,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAI,cAAc,IAAI,CAAC,IAAI,cAAc,IAAI,CAAC,EAAE,CAAC;QACnF,MAAM,IAAI,KAAK,CAAC,gEAAgE,CAAC,CAAC;IACpF,CAAC;IACD,OAAO,CAAC,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,cAAc,EAAE,YAAY,EAAE,EAAE,EAAE;QACnF,IAAI,UAAU,IAAI,cAAc,EAAE,CAAC;YACjC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,cAAc,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;QACrF,CAAC;QACD,IAAI,CAAC,YAAY,IAAI,UAAU,KAAK,CAAC,IAAI,cAAc,EAAE,CAAC;YACxD,OAAO,EAAE,QAAQ,EAAE,WAAW,GAAG,UAAU,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;QACtE,CAAC;QACD,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;IACzD,CAAC,CAAC;AACJ,CAAC;
|
|
1
|
+
{"version":3,"file":"adaptive-rate.js","sourceRoot":"","sources":["../src/adaptive-rate.ts"],"names":[],"mappings":"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"}
|
package/dist/scheduler.js
CHANGED
|
@@ -21,7 +21,7 @@ export function createScheduler(options, adaptiveRateOptions, retryPolicy) {
|
|
|
21
21
|
let cooldownTotal = 0;
|
|
22
22
|
/** Monotonic (`performance.now()`) time before which nothing starts; `undefined` when no cooldown is active. */
|
|
23
23
|
let cooldownDeadline;
|
|
24
|
-
/** Fires at the deadline
|
|
24
|
+
/** Fires at the deadline, armed only while queued work waits on it; `start()` arms it when work arrives. */
|
|
25
25
|
let cooldownTimer;
|
|
26
26
|
const abortController = new AbortController();
|
|
27
27
|
/** Submitted items whose result has not settled. */
|
|
@@ -52,12 +52,12 @@ export function createScheduler(options, adaptiveRateOptions, retryPolicy) {
|
|
|
52
52
|
switch (next) {
|
|
53
53
|
case "running":
|
|
54
54
|
work.thaw();
|
|
55
|
-
if (
|
|
56
|
-
|
|
55
|
+
if (coolingDown()) {
|
|
56
|
+
armCooldown();
|
|
57
|
+
adaptiveRate.suspend();
|
|
57
58
|
return;
|
|
58
59
|
}
|
|
59
|
-
|
|
60
|
-
adaptiveRate.suspend();
|
|
60
|
+
adaptiveRate.start();
|
|
61
61
|
return;
|
|
62
62
|
case "paused":
|
|
63
63
|
// A cooldown keeps elapsing while paused: its deadline is the server's, not the queue's.
|
|
@@ -84,20 +84,27 @@ export function createScheduler(options, adaptiveRateOptions, retryPolicy) {
|
|
|
84
84
|
rejectTasks(error);
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
-
|
|
87
|
+
/** Re-arms the expiry timer for the current deadline, or leaves it unarmed while nothing is queued. */
|
|
88
|
+
function armCooldown() {
|
|
88
89
|
clearTimeout(cooldownTimer);
|
|
89
|
-
cooldownTimer =
|
|
90
|
+
cooldownTimer = cooldownDeadline !== undefined && work.queued > 0
|
|
91
|
+
? setTimeout(expireCooldown, Math.max(0, cooldownDeadline - performance.now()))
|
|
92
|
+
: undefined;
|
|
90
93
|
}
|
|
91
94
|
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
95
|
cooldownTimer = undefined;
|
|
98
|
-
|
|
96
|
+
// A timer can fire slightly before the monotonic deadline; `start()` then waits out the remainder.
|
|
99
97
|
start();
|
|
100
98
|
}
|
|
99
|
+
/** Ends a cooldown whose deadline has passed; returns whether one still holds starts. */
|
|
100
|
+
function coolingDown() {
|
|
101
|
+
if (cooldownDeadline === undefined)
|
|
102
|
+
return false;
|
|
103
|
+
if (performance.now() < cooldownDeadline)
|
|
104
|
+
return true;
|
|
105
|
+
cooldownDeadline = undefined;
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
101
108
|
function clearCooldown() {
|
|
102
109
|
clearTimeout(cooldownTimer);
|
|
103
110
|
cooldownTimer = undefined;
|
|
@@ -181,9 +188,14 @@ export function createScheduler(options, adaptiveRateOptions, retryPolicy) {
|
|
|
181
188
|
}
|
|
182
189
|
handleSettlement(item, returnedOutcome(item, result), result, reportSettlement);
|
|
183
190
|
}
|
|
191
|
+
/** Lets due work start; during a cooldown, keeps the expiry timer armed exactly while work is queued. */
|
|
184
192
|
function start() {
|
|
185
|
-
if (lifecycle.state
|
|
193
|
+
if (lifecycle.state !== "running")
|
|
194
|
+
return;
|
|
195
|
+
if (!coolingDown())
|
|
186
196
|
adaptiveRate.start();
|
|
197
|
+
else if (cooldownTimer === undefined || work.queued === 0)
|
|
198
|
+
armCooldown();
|
|
187
199
|
}
|
|
188
200
|
/** Admits `item` or throws: the queue is terminal or every capacity reservation is taken. */
|
|
189
201
|
function accept(item) {
|
|
@@ -217,10 +229,14 @@ export function createScheduler(options, adaptiveRateOptions, retryPolicy) {
|
|
|
217
229
|
if (!tasks.delete(task))
|
|
218
230
|
return;
|
|
219
231
|
cnt_canceled++;
|
|
220
|
-
|
|
232
|
+
const queued = !task.active;
|
|
233
|
+
if (queued)
|
|
221
234
|
work.remove(item);
|
|
222
235
|
task.controller.abort(reason);
|
|
223
236
|
task.reject(task.controller.signal.reason);
|
|
237
|
+
// The removed item may have been the last queued: pacing can drain and a cooldown timer unarm.
|
|
238
|
+
if (queued)
|
|
239
|
+
start();
|
|
224
240
|
},
|
|
225
241
|
};
|
|
226
242
|
};
|
|
@@ -234,19 +250,19 @@ export function createScheduler(options, adaptiveRateOptions, retryPolicy) {
|
|
|
234
250
|
if (lifecycle.state === "aborted" || lifecycle.state === "failed")
|
|
235
251
|
return;
|
|
236
252
|
const now = performance.now();
|
|
253
|
+
const beginning = !coolingDown();
|
|
237
254
|
const latest = Math.max(now, cooldownDeadline ?? now);
|
|
238
255
|
const deadline = now + Math.min(delay, maxCooldown);
|
|
239
256
|
// Hints are absolute ("not before now + delay"), so overlapping ones keep the later deadline, never add up.
|
|
240
257
|
if (deadline <= latest)
|
|
241
258
|
return;
|
|
242
|
-
const beginning = cooldownDeadline === undefined;
|
|
243
259
|
cnt_cooldowns++;
|
|
244
260
|
cooldownTotal += deadline - latest;
|
|
245
261
|
cooldownDeadline = deadline;
|
|
246
262
|
// Stopped: the restart arms the timer for whatever remains.
|
|
247
263
|
if (lifecycle.state === "stopped")
|
|
248
264
|
return;
|
|
249
|
-
armCooldown(
|
|
265
|
+
armCooldown();
|
|
250
266
|
// Paused: adaptive rate is already stopped; `resume()` suspends it instead of starting it.
|
|
251
267
|
if (beginning && lifecycle.state === "running")
|
|
252
268
|
adaptiveRate.suspend();
|
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;AAExD,OAAO,EAAE,iBAAiB,EAAE,0BAA0B;AA4EtD,MAAM,aAAa,GAAmB,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,CAAC;AAOhF,+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,
|
|
1
|
+
{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dynamic-throttled-queue",
|
|
3
|
-
"version": "3.0.0-rc.
|
|
3
|
+
"version": "3.0.0-rc.6e88a00",
|
|
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": [
|
|
7
|
-
"dist"
|
|
7
|
+
"dist",
|
|
8
|
+
"skills/configure-throttled-queue"
|
|
8
9
|
],
|
|
9
10
|
"zshy": {
|
|
10
11
|
"exports": {
|
|
@@ -22,7 +23,8 @@
|
|
|
22
23
|
"queue",
|
|
23
24
|
"throttle",
|
|
24
25
|
"throttled",
|
|
25
|
-
"request"
|
|
26
|
+
"request",
|
|
27
|
+
"tanstack-intent"
|
|
26
28
|
],
|
|
27
29
|
"author": "Adrian Elton-Browning <gingacodemonkey@gmail.com>",
|
|
28
30
|
"license": "MIT",
|
|
@@ -33,6 +35,7 @@
|
|
|
33
35
|
"@commitlint/cli": "21.2.3",
|
|
34
36
|
"@commitlint/config-conventional": "20.0.0",
|
|
35
37
|
"@gingacodemonkey/config": "^0.0.38",
|
|
38
|
+
"@tanstack/intent": "0.5.0",
|
|
36
39
|
"@types/node": "^24.13.3",
|
|
37
40
|
"@varlock/bumpy": "^1.18.1",
|
|
38
41
|
"eslint": "^9.39.5",
|
|
@@ -40,7 +43,6 @@
|
|
|
40
43
|
"jscpd": "^4.2.5",
|
|
41
44
|
"knip": "5.70.1",
|
|
42
45
|
"lint-staged": "17.6.0",
|
|
43
|
-
"only-allow": "^1.2.2",
|
|
44
46
|
"typescript": "^6.0.3",
|
|
45
47
|
"vitest": "^5.0.2",
|
|
46
48
|
"zshy": "^0.8.0"
|
|
@@ -70,8 +72,8 @@
|
|
|
70
72
|
"lint:jscpd": "jscpd .",
|
|
71
73
|
"lint": "pnpm lint:ts && pnpm lint:esl",
|
|
72
74
|
"lint:e18e": "pnpm dlx @e18e/cli analyze",
|
|
73
|
-
"preinstall": "only-allow pnpm",
|
|
74
75
|
"bump": "bumpy add",
|
|
75
|
-
"bump:status": "bumpy status"
|
|
76
|
+
"bump:status": "bumpy status",
|
|
77
|
+
"skills:check": "intent maintainer check"
|
|
76
78
|
}
|
|
77
79
|
}
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: configure-throttled-queue
|
|
3
|
+
description: >
|
|
4
|
+
Use when creating, configuring, or debugging a dynamic-throttled-queue
|
|
5
|
+
(createThrottledQueue): pacing calls to a rate-limited API, choosing
|
|
6
|
+
min_rpi/max_rpi/interval/concurrency, adapting the rate to errors or HTTP
|
|
7
|
+
429s, retrying failed work, honoring Retry-After, getting results back with
|
|
8
|
+
submit() and cancelling them, or pausing, stopping, aborting, and draining
|
|
9
|
+
the queue.
|
|
10
|
+
metadata:
|
|
11
|
+
purpose: >
|
|
12
|
+
Teach agents to configure dynamic-throttled-queue correctly: start-rate
|
|
13
|
+
pacing versus async concurrency, adaptive-rate outcome classification
|
|
14
|
+
versus retry eligibility, server-directed cooldowns, task handles, and the
|
|
15
|
+
pause/resume/stop/abort/failed lifecycle semantics.
|
|
16
|
+
type: core
|
|
17
|
+
library: dynamic-throttled-queue
|
|
18
|
+
library_version: 3.0.0
|
|
19
|
+
sources:
|
|
20
|
+
- src/*.ts
|
|
21
|
+
- src/__tests__/*.test.ts
|
|
22
|
+
# Keep the literal directory prefix at 15 characters or fewer. With Intent's
|
|
23
|
+
# ":(exclude)**/node_modules/**" pathspec, git ls-files (seen on 2.39 and
|
|
24
|
+
# 2.55) lists nothing under most longer prefixes, so review loses the fixtures.
|
|
25
|
+
- tests/skills/configure-throttled-queue*/*
|
|
26
|
+
- README.md
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
# Configure a dynamic-throttled-queue
|
|
30
|
+
|
|
31
|
+
`createThrottledQueue(options)` returns a function that enqueues callbacks, plus
|
|
32
|
+
control methods. Work through the steps in order; each ends with a check.
|
|
33
|
+
|
|
34
|
+
## Setup
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { createThrottledQueue } from "dynamic-throttled-queue";
|
|
38
|
+
|
|
39
|
+
// At most 5 request starts per second, spread evenly (one every 200 ms).
|
|
40
|
+
const queue = createThrottledQueue({ min_rpi: 5, interval: 1000 });
|
|
41
|
+
|
|
42
|
+
queue(async ({ signal }) => {
|
|
43
|
+
const response = await fetch("https://api.example.com/data", { signal });
|
|
44
|
+
if (!response.ok) return false; // fire-and-forget failure signal
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The package is ESM-only and requires Node.js 24 or later.
|
|
49
|
+
|
|
50
|
+
## 1. Pick the start rate
|
|
51
|
+
|
|
52
|
+
The rate counts **callback starts per `interval` milliseconds**. It says nothing
|
|
53
|
+
about how long a callback runs.
|
|
54
|
+
|
|
55
|
+
| Option | Meaning | Rule |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| `min_rpi` | Lowest starts per interval | Required positive integer |
|
|
58
|
+
| `max_rpi` | Highest starts per interval | Integer `>= min_rpi`; defaults to `min_rpi` |
|
|
59
|
+
| `interval` | Window length in ms | Required positive number |
|
|
60
|
+
| `evenly_spaced` | `true` (default): one start every `interval / rate` ms. `false`: up to `rate` starts at once per interval | |
|
|
61
|
+
|
|
62
|
+
- A provider limit of "100 requests per minute" is `{ min_rpi: 100, interval: 60_000 }`.
|
|
63
|
+
Use `evenly_spaced: false` only when bursts are allowed.
|
|
64
|
+
- With `max_rpi === min_rpi` the rate is fixed. With a range, the queue starts at
|
|
65
|
+
`Math.ceil((min_rpi + max_rpi) / 2)` and adapts between the bounds (step 3).
|
|
66
|
+
- Set `max_rpi` to the provider's documented ceiling, never above it: the default
|
|
67
|
+
strategy raises the rate after every clean interval that has pending work.
|
|
68
|
+
|
|
69
|
+
Check: `createThrottledQueue` throws synchronously on invalid numbers, and
|
|
70
|
+
`queue.getState().rate` shows the starting rate.
|
|
71
|
+
|
|
72
|
+
## 2. Bound in-flight work separately
|
|
73
|
+
|
|
74
|
+
`concurrency` caps callbacks still awaiting asynchronous settlement. It is
|
|
75
|
+
independent of the start rate; omitting it allows unlimited in-flight work.
|
|
76
|
+
Use both when the provider limits requests per window **and** simultaneous
|
|
77
|
+
connections:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { createThrottledQueue } from "dynamic-throttled-queue";
|
|
81
|
+
|
|
82
|
+
const queue = createThrottledQueue({
|
|
83
|
+
min_rpi: 10,
|
|
84
|
+
interval: 1000,
|
|
85
|
+
concurrency: 3, // never more than 3 unsettled callbacks
|
|
86
|
+
maxQueueSize: 1000, // bound accepted, not-yet-terminal work
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
When `maxQueueSize` is reached, `queue(...)` and `submit(...)` **throw
|
|
91
|
+
synchronously** (`Cannot enqueue work: maxQueueSize has been reached`). Capacity covers
|
|
92
|
+
pending, active, and retrying work until each item is terminal.
|
|
93
|
+
|
|
94
|
+
Check: `getState().active` never exceeds `concurrency`.
|
|
95
|
+
|
|
96
|
+
## 3. Decide which failures adapt the rate
|
|
97
|
+
|
|
98
|
+
A failure is a callback that returns `false`, throws, or rejects. By default
|
|
99
|
+
every failure counts toward `errors_per_interval` (default `5`). When the count
|
|
100
|
+
reaches the threshold, the default `linear` strategy lowers the rate by 1; an
|
|
101
|
+
interval with zero errors and pending work raises it by 1. `back_off: true`
|
|
102
|
+
also skips one full interval after the threshold is hit.
|
|
103
|
+
|
|
104
|
+
Count only capacity signals with `rateOutcomeClassifier`. It receives
|
|
105
|
+
`{ kind: "returned-false" }`, `{ kind: "thrown", error }`, or
|
|
106
|
+
`{ kind: "rejected", error }` and returns whether the failure reduces the rate:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import { createThrottledQueue, type FailureOutcome } from "dynamic-throttled-queue";
|
|
110
|
+
|
|
111
|
+
class HttpError extends Error {
|
|
112
|
+
constructor(readonly status: number) {
|
|
113
|
+
super(`HTTP ${status}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function isCapacitySignal(outcome: FailureOutcome): boolean {
|
|
118
|
+
if (outcome.kind === "returned-false") return false;
|
|
119
|
+
const { error } = outcome;
|
|
120
|
+
return error instanceof HttpError && (error.status === 429 || error.status >= 500);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const queue = createThrottledQueue({
|
|
124
|
+
min_rpi: 1,
|
|
125
|
+
max_rpi: 20,
|
|
126
|
+
interval: 1000,
|
|
127
|
+
errors_per_interval: 3,
|
|
128
|
+
back_off: true,
|
|
129
|
+
rateOutcomeClassifier: isCapacitySignal,
|
|
130
|
+
onRateChange: rate => console.log(`rate is now ${rate}/s`),
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
If the classifier throws, the failure counts as rate-reducing. To change how
|
|
135
|
+
the rate moves (for example AIMD), read
|
|
136
|
+
[rate strategies and timing](references/rate-strategies.md).
|
|
137
|
+
|
|
138
|
+
Check: `getState().rateDecreases` grows only for the failures you classify as
|
|
139
|
+
capacity signals.
|
|
140
|
+
|
|
141
|
+
## 4. Configure retries independently
|
|
142
|
+
|
|
143
|
+
`retry` is the hard cap on extra attempts (default `0`). `retryClassifier`
|
|
144
|
+
decides eligibility per failure and must return literal `true` to retry.
|
|
145
|
+
`retryBackoff` delays retries; without it they are immediate.
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import { createThrottledQueue } from "dynamic-throttled-queue";
|
|
149
|
+
|
|
150
|
+
const queue = createThrottledQueue({
|
|
151
|
+
min_rpi: 5,
|
|
152
|
+
interval: 1000,
|
|
153
|
+
retry: 3,
|
|
154
|
+
retryClassifier: (outcome, attempt) => outcome.kind !== "returned-false" && attempt <= 3,
|
|
155
|
+
retryBackoff: { strategy: "exponential", baseDelay: 250, maxDelay: 10_000, jitter: 0.2 },
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Retry and rate decisions are separate: a failure can be retryable,
|
|
160
|
+
rate-reducing, both, or neither. Every retry is a new attempt that obeys normal
|
|
161
|
+
pacing. For backoff formulas and validation, read
|
|
162
|
+
[retry details](references/retries.md).
|
|
163
|
+
|
|
164
|
+
Check: `getState().retried` counts scheduled retries; the callback's
|
|
165
|
+
`attempt` (1-based) counts up across retries.
|
|
166
|
+
|
|
167
|
+
## 5. Honor server cooldowns
|
|
168
|
+
|
|
169
|
+
When a server says when capacity returns (HTTP 429 with `Retry-After`), call
|
|
170
|
+
`cooldownFor(ms)`. The queue starts nothing new until then, keeps pending
|
|
171
|
+
work, and makes no rate decision meanwhile. It does not retry or lower the rate
|
|
172
|
+
on its own; combine it with steps 3 and 4.
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
import { createThrottledQueue } from "dynamic-throttled-queue";
|
|
176
|
+
|
|
177
|
+
const queue = createThrottledQueue({ min_rpi: 1, max_rpi: 10, interval: 1000, retry: 2, maxCooldown: 60_000 });
|
|
178
|
+
|
|
179
|
+
const task = queue.submit(async ({ signal }) => {
|
|
180
|
+
const response = await fetch("https://api.example.com/data", { signal });
|
|
181
|
+
if (response.status === 429) {
|
|
182
|
+
const seconds = Number(response.headers.get("Retry-After"));
|
|
183
|
+
if (Number.isFinite(seconds) && seconds >= 0) queue.cooldownFor(seconds * 1000);
|
|
184
|
+
throw new Error("rate limited");
|
|
185
|
+
}
|
|
186
|
+
return response.json() as Promise<unknown>;
|
|
187
|
+
});
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Convert the header yourself and clamp past dates to `0`: `NaN`, negative, and
|
|
191
|
+
infinite delays throw `RangeError`. Delays above `maxCooldown` are clamped. A
|
|
192
|
+
later deadline replaces an earlier one; overlapping hints do not add up.
|
|
193
|
+
|
|
194
|
+
Check: `getState().cooldownRemaining` is non-zero during the cooldown.
|
|
195
|
+
|
|
196
|
+
## 6. Get results with task handles
|
|
197
|
+
|
|
198
|
+
Use `submit()` when the caller needs the value. Its semantics differ from the
|
|
199
|
+
fire-and-forget form:
|
|
200
|
+
|
|
201
|
+
- Every returned value, **including `false`**, is a successful result. Only a
|
|
202
|
+
throw or rejection fails, retries, and counts for the rate.
|
|
203
|
+
- `result` settles once: the final value, the last attempt's error, or the
|
|
204
|
+
cancel reason.
|
|
205
|
+
- `cancel(reason?)` rejects `result` immediately (an `AbortError` by default).
|
|
206
|
+
A pending item is removed; an active one has only its own signal aborted and
|
|
207
|
+
keeps its concurrency slot until the callback returns. Cancelled work never
|
|
208
|
+
retries and never counts as success, failure, or a rate error.
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
import { createThrottledQueue } from "dynamic-throttled-queue";
|
|
212
|
+
|
|
213
|
+
const queue = createThrottledQueue({ min_rpi: 5, interval: 1000, retry: 2 });
|
|
214
|
+
|
|
215
|
+
const task = queue.submit(async ({ signal, attempt }) => {
|
|
216
|
+
const response = await fetch("https://api.example.com/item", { signal });
|
|
217
|
+
if (!response.ok) throw new Error(`HTTP ${response.status} on attempt ${attempt}`);
|
|
218
|
+
return (await response.json()) as { id: string };
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
setTimeout(() => task.cancel(), 5000);
|
|
222
|
+
const item = await task.result.catch(() => undefined);
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Check: every `result` you cancel or abort has a rejection handler.
|
|
226
|
+
|
|
227
|
+
## 7. Control the lifecycle
|
|
228
|
+
|
|
229
|
+
| Call | Effect | Reversible |
|
|
230
|
+
| --- | --- | --- |
|
|
231
|
+
| `pause()` | No new starts; keeps work; active callbacks finish | `resume()` |
|
|
232
|
+
| `stop()` | Clears timers; keeps work | The next enqueue restarts it |
|
|
233
|
+
| `abort()` | Discards pending work, aborts the shared signal, later enqueues throw | No |
|
|
234
|
+
|
|
235
|
+
A throwing or malformed `rateStrategy` moves the queue to `failed`: terminal,
|
|
236
|
+
pending work discarded, enqueues throw the strategy error, and `waitForIdle()`
|
|
237
|
+
promises reject with it.
|
|
238
|
+
Create a new queue to continue. For per-state rules, read
|
|
239
|
+
[lifecycle details](references/lifecycle.md) before writing shutdown,
|
|
240
|
+
pause, or error-recovery code.
|
|
241
|
+
|
|
242
|
+
Drain and inspect:
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
import { createThrottledQueue } from "dynamic-throttled-queue";
|
|
246
|
+
|
|
247
|
+
const queue = createThrottledQueue({ min_rpi: 5, interval: 1000 });
|
|
248
|
+
for (const id of ["a", "b", "c"]) {
|
|
249
|
+
queue(async ({ signal }) => {
|
|
250
|
+
await fetch(`https://api.example.com/items/${id}`, { signal });
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
await queue.waitForIdle(); // pending, active, and delayed retries all terminal
|
|
255
|
+
const { succeeded, failed, state } = queue.getState();
|
|
256
|
+
console.log({ succeeded, failed, state });
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
For every `getState()` field and `waitForIdle()` edge case, read
|
|
260
|
+
[state and idle](references/state-and-idle.md).
|
|
261
|
+
|
|
262
|
+
Check: `waitForIdle()` resolves and `getState().pending` and `.active` are `0`.
|
|
263
|
+
|
|
264
|
+
## Common Mistakes
|
|
265
|
+
|
|
266
|
+
### CRITICAL: Ignoring the signal and expecting abort or cancel to stop work
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
import { createThrottledQueue } from "dynamic-throttled-queue";
|
|
270
|
+
|
|
271
|
+
const queue = createThrottledQueue({ min_rpi: 5, interval: 1000 });
|
|
272
|
+
// Wrong: abort() cannot stop this request
|
|
273
|
+
queue(async () => { await fetch("https://api.example.com/data"); });
|
|
274
|
+
// Right
|
|
275
|
+
queue(async ({ signal }) => { await fetch("https://api.example.com/data", { signal }); });
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Cancellation is cooperative. A callback that ignores `signal` keeps running
|
|
279
|
+
after `abort()` or `cancel()`; only its outcome is discarded. Source: README
|
|
280
|
+
"Lifecycle"; `src/scheduler.ts`.
|
|
281
|
+
|
|
282
|
+
### CRITICAL: Starting a promise without returning or awaiting it
|
|
283
|
+
|
|
284
|
+
```ts
|
|
285
|
+
import { createThrottledQueue } from "dynamic-throttled-queue";
|
|
286
|
+
|
|
287
|
+
const queue = createThrottledQueue({ min_rpi: 5, interval: 1000, concurrency: 2, retry: 2 });
|
|
288
|
+
// Wrong: the queue sees a synchronous success; no retry, no concurrency slot
|
|
289
|
+
queue(({ signal }) => { void fetch("https://api.example.com/data", { signal }).catch(() => {}); });
|
|
290
|
+
// Right: return (or await) the work
|
|
291
|
+
queue(({ signal }) => fetch("https://api.example.com/data", { signal }));
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
The queue only tracks what the callback returns. An unreturned promise cannot
|
|
295
|
+
fail, retry, hold a `concurrency` slot, or delay `waitForIdle()`. Source: README
|
|
296
|
+
"Queuing work".
|
|
297
|
+
|
|
298
|
+
### HIGH: Using the rate to limit simultaneous requests
|
|
299
|
+
|
|
300
|
+
`min_rpi: 3` allows 3 starts per interval; slow callbacks pile up without
|
|
301
|
+
bound. Add `concurrency` for an in-flight cap. Source: `ThrottleOptions.concurrency`.
|
|
302
|
+
|
|
303
|
+
### HIGH: Returning `false` from `submit()` to signal failure
|
|
304
|
+
|
|
305
|
+
In `submit()`, `false` is a successful result: no retry, no rate reduction.
|
|
306
|
+
Throw instead. Fire-and-forget callbacks still treat `false` as failure; any
|
|
307
|
+
other returned value is success. Source: `TaskCallback` and `ThrottleCallback`
|
|
308
|
+
in `src/scheduler.ts`.
|
|
309
|
+
|
|
310
|
+
### HIGH: Expecting `retryClassifier` or cooldowns to change the rate
|
|
311
|
+
|
|
312
|
+
Retry eligibility, rate counting (`rateOutcomeClassifier`), and `cooldownFor`
|
|
313
|
+
are three independent decisions. Configure each one you need. Source: README
|
|
314
|
+
"Failure classification" and "Server-directed cooldown".
|
|
315
|
+
|
|
316
|
+
### HIGH: Unhandled rejections from cancelled or aborted tasks
|
|
317
|
+
|
|
318
|
+
`cancel()` and `abort()` reject `task.result`. Attach a handler such as
|
|
319
|
+
`task.result.catch(() => {})` if you do not await it.
|
|
320
|
+
|
|
321
|
+
### MEDIUM: Treating `stop()` as shutdown
|
|
322
|
+
|
|
323
|
+
`stop()` keeps pending work and the next enqueue restarts processing. Use
|
|
324
|
+
`abort()` for terminal shutdown.
|
|
325
|
+
|
|
326
|
+
### MEDIUM: Catching the `maxQueueSize` overflow as a return value
|
|
327
|
+
|
|
328
|
+
A full queue throws from `queue(...)` and `submit(...)`. Wrap enqueue
|
|
329
|
+
calls in `try`/`catch` when `maxQueueSize` is set.
|
|
330
|
+
|
|
331
|
+
## Completion
|
|
332
|
+
|
|
333
|
+
The configuration is complete when:
|
|
334
|
+
|
|
335
|
+
1. The start rate matches the provider limit and `max_rpi` does not exceed it.
|
|
336
|
+
2. `concurrency` is set when the provider limits simultaneous requests.
|
|
337
|
+
3. Each failure type is deliberately classified for rate reduction and retry.
|
|
338
|
+
4. Every async callback passes `signal` to its I/O.
|
|
339
|
+
5. Every `submit()` result is awaited or has a rejection handler.
|
|
340
|
+
6. Shutdown uses `abort()` or `waitForIdle()`, not `stop()`.
|
|
341
|
+
|
|
342
|
+
If the queue reports `state: "failed"`, fix the custom `rateStrategy` and
|
|
343
|
+
create a new queue.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Lifecycle details
|
|
2
|
+
|
|
3
|
+
Read this before writing shutdown, pause/resume, or error-recovery code, or
|
|
4
|
+
when a queue stops starting work unexpectedly.
|
|
5
|
+
|
|
6
|
+
| State | Pending work and enqueue | Active work | Lifecycle calls |
|
|
7
|
+
| --- | --- | --- | --- |
|
|
8
|
+
| `running` | Starts normally; enqueues accepted | Runs | `pause()` retains work and stops starts. `stop()` retains work and clears timers. `abort()` is terminal. A strategy failure moves to `failed`. |
|
|
9
|
+
| `paused` | Pending work, new enqueues, and retries are kept but do not start | Continues and settles | `resume()` restarts pacing at the current rate with a fresh observation window. `pause()` is idempotent. `stop()` clears the paused state. |
|
|
10
|
+
| `stopped` | Pending work kept; the next enqueue restarts scheduling | Continues and settles | `pause()` and `resume()` do nothing. `stop()` is idempotent. |
|
|
11
|
+
| `aborted` | Pending work discarded; enqueues throw | Receives the aborted shared signal and may finish cooperatively | Nothing restarts scheduling. `abort()` is idempotent. |
|
|
12
|
+
| `failed` | Pending work and delayed retries discarded; enqueues throw the strategy error | Continues and settles without retries or accounting; the shared signal is **not** aborted | Terminal. All lifecycle calls do nothing. |
|
|
13
|
+
|
|
14
|
+
## Rules that are easy to miss
|
|
15
|
+
|
|
16
|
+
- While paused, outcomes do not adjust the rate. A failure can still create a
|
|
17
|
+
retry, which waits for `resume()`.
|
|
18
|
+
- `stop()` cannot cancel an active callback, and that callback settling does not
|
|
19
|
+
restart scheduling. Only a new enqueue does, and it also resumes frozen
|
|
20
|
+
delayed retries.
|
|
21
|
+
- `abort()` aborts one shared signal for fire-and-forget callbacks; `submit()`
|
|
22
|
+
callbacks get a per-item signal that `abort()` also aborts. Late outcomes are
|
|
23
|
+
discarded and never retry.
|
|
24
|
+
- On `failed`, the strategy error is rethrown where the decision ran: as an
|
|
25
|
+
uncaught exception from the adaptive-rate timer, or as an unhandled rejection
|
|
26
|
+
when a slow async callback completes a settled-timing window. Pending and
|
|
27
|
+
later `waitForIdle()` calls reject with it. Create a new queue to resume.
|
|
28
|
+
|
|
29
|
+
## Cooldown interaction
|
|
30
|
+
|
|
31
|
+
- `pause()` and `cooldownFor()` are separate. A cooldown keeps elapsing while
|
|
32
|
+
paused, its end does not resume a paused queue, and `resume()` does not end it.
|
|
33
|
+
- `stop()` clears the cooldown timer but keeps the deadline, so the restarting
|
|
34
|
+
enqueue still waits for it.
|
|
35
|
+
- `abort()` and a strategy failure clear the cooldown; later valid
|
|
36
|
+
`cooldownFor()` calls do nothing, but invalid delays still throw `RangeError`.
|
|
37
|
+
- A cooldown replaces any adaptive-rate backoff in progress. Failures that
|
|
38
|
+
settle during a cooldown count toward the first decision after it.
|
|
39
|
+
|
|
40
|
+
## Task handles across the lifecycle
|
|
41
|
+
|
|
42
|
+
- `pause()` and `stop()` leave `result` pending.
|
|
43
|
+
- `abort()` rejects every unsettled `result` with the queue signal's
|
|
44
|
+
`AbortError`; a strategy failure rejects them with the strategy error.
|
|
45
|
+
- `submit()` throws synchronously and returns no handle when the queue is full,
|
|
46
|
+
aborted, or failed.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Rate strategies and adjustment timing
|
|
2
|
+
|
|
3
|
+
Read this when the default one-step `linear` adaptation is too slow or too
|
|
4
|
+
fast, when writing a custom `RateStrategy`, or when slow callbacks make rate
|
|
5
|
+
decisions land in the wrong interval.
|
|
6
|
+
|
|
7
|
+
## Built-in strategies
|
|
8
|
+
|
|
9
|
+
| Strategy | Threshold reached | Clean interval with pending work | Otherwise |
|
|
10
|
+
| --- | --- | --- | --- |
|
|
11
|
+
| `linear` (default) | `currentRate - 1`, request backoff | `currentRate + 1` | hold |
|
|
12
|
+
| `aimd({ increaseBy = 1, decreaseFactor = 0.5 })` | `Math.floor(currentRate * decreaseFactor)`, request backoff | `currentRate + increaseBy` | hold |
|
|
13
|
+
|
|
14
|
+
A backoff request (`shouldBackOff: true`) skips an interval only when the queue
|
|
15
|
+
has `back_off: true`.
|
|
16
|
+
|
|
17
|
+
"Clean" means zero counted errors and the previous window was not a backoff.
|
|
18
|
+
Partial-error, empty, and immediately-post-backoff windows hold the rate. The
|
|
19
|
+
queue clamps every `nextRate` to `[min_rpi, max_rpi]`. `aimd()` throws unless
|
|
20
|
+
`increaseBy` is a positive integer and `0 < decreaseFactor < 1`.
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { aimd, createThrottledQueue } from "dynamic-throttled-queue";
|
|
24
|
+
|
|
25
|
+
const queue = createThrottledQueue({
|
|
26
|
+
min_rpi: 1,
|
|
27
|
+
max_rpi: 100,
|
|
28
|
+
interval: 1000,
|
|
29
|
+
rateStrategy: aimd({ increaseBy: 2, decreaseFactor: 0.5 }),
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Custom strategies
|
|
34
|
+
|
|
35
|
+
A `RateStrategy` is a pure function. It receives a frozen observation
|
|
36
|
+
`{ currentRate, minRate, maxRate, errorCount, errorThreshold, hasPendingWork, wasBackedOff }`
|
|
37
|
+
and returns `{ nextRate, shouldBackOff }`.
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { createThrottledQueue, type RateStrategy } from "dynamic-throttled-queue";
|
|
41
|
+
|
|
42
|
+
const decreaseOnly: RateStrategy = ({ currentRate, minRate, errorCount, errorThreshold }) => ({
|
|
43
|
+
nextRate: errorCount >= errorThreshold ? Math.max(minRate, currentRate - 1) : currentRate,
|
|
44
|
+
shouldBackOff: errorCount >= errorThreshold,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const queue = createThrottledQueue({ min_rpi: 1, max_rpi: 10, interval: 1000, rateStrategy: decreaseOnly });
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
- `nextRate` must be a finite integer and `shouldBackOff` a boolean. Anything
|
|
51
|
+
else, or a thrown error, **fails the queue permanently** (see
|
|
52
|
+
[lifecycle](lifecycle.md)). Never return `NaN`, fractions, or `undefined`.
|
|
53
|
+
- `shouldBackOff` only pauses scheduling when the queue has `back_off: true`.
|
|
54
|
+
|
|
55
|
+
## Adjustment timing
|
|
56
|
+
|
|
57
|
+
- `adjustmentTiming: "interval"` (default): each interval decides from the
|
|
58
|
+
outcomes that have settled so far. A slow callback can settle after its start
|
|
59
|
+
interval and affect a later decision.
|
|
60
|
+
- `adjustmentTiming: "settled"`: a window holds every attempt started during one
|
|
61
|
+
interval. When collection closes, no further callbacks start until every
|
|
62
|
+
attempt in the window settles, then exactly one decision is made. Use it when
|
|
63
|
+
callbacks run longer than `interval` and decisions must reflect complete
|
|
64
|
+
windows. A callback that never settles blocks later windows until it settles
|
|
65
|
+
or the queue is aborted, so give callbacks timeouts.
|
|
66
|
+
|
|
67
|
+
While paused, settled outcomes do not count toward the rate. In settled timing,
|
|
68
|
+
`pause()` discards the open window.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Retry details
|
|
2
|
+
|
|
3
|
+
Read this when choosing a `retryBackoff` policy, when `createThrottledQueue`
|
|
4
|
+
rejects retry options, or when retry counts in `getState()` look wrong.
|
|
5
|
+
|
|
6
|
+
## Eligibility
|
|
7
|
+
|
|
8
|
+
1. A failure (`false` in fire-and-forget form, a throw, or a rejection) is
|
|
9
|
+
eligible only while `retry` budget remains. `retry` must be a non-negative
|
|
10
|
+
integer.
|
|
11
|
+
2. If `retryClassifier` is set, it is called with the normalized outcome and the
|
|
12
|
+
one-based attempt that just failed. Only literal `true` retries; any other
|
|
13
|
+
value makes the failure permanent. It is not called once the budget is spent.
|
|
14
|
+
3. If the classifier throws, the queue retries within the remaining budget.
|
|
15
|
+
|
|
16
|
+
Cancelled tasks, and outcomes that settle after `abort()` or a strategy
|
|
17
|
+
failure, never retry.
|
|
18
|
+
|
|
19
|
+
## Backoff
|
|
20
|
+
|
|
21
|
+
`retryBackoff` is
|
|
22
|
+
`{ strategy: "fixed" | "linear" | "exponential"; baseDelay: number; maxDelay?: number; jitter?: number; random?: () => number }`.
|
|
23
|
+
|
|
24
|
+
For retry index `n` (first retry is `1`):
|
|
25
|
+
|
|
26
|
+
| Strategy | Delay before cap |
|
|
27
|
+
| --- | --- |
|
|
28
|
+
| `fixed` | `baseDelay` |
|
|
29
|
+
| `linear` | `baseDelay × n` |
|
|
30
|
+
| `exponential` | `baseDelay × 2^(n - 1)` |
|
|
31
|
+
|
|
32
|
+
The delay is capped at `maxDelay`, symmetric percentage `jitter` (`0`–`1`) is
|
|
33
|
+
applied, then it is capped again. `baseDelay` and `maxDelay` must be finite and
|
|
34
|
+
non-negative; an unknown `strategy` throws. Pass `random` to make jitter
|
|
35
|
+
deterministic in tests; a throwing or out-of-range `random` falls back to no
|
|
36
|
+
jitter.
|
|
37
|
+
|
|
38
|
+
Backoff starts when the failed attempt settles. It is independent of the
|
|
39
|
+
adaptive-rate `back_off` option.
|
|
40
|
+
|
|
41
|
+
## Scheduling
|
|
42
|
+
|
|
43
|
+
Delayed retries count as pending work and occupy `maxQueueSize` capacity. When
|
|
44
|
+
due, they rejoin the tail of the queue and obey normal pacing; equal due times
|
|
45
|
+
keep their scheduling order. While paused or stopped, their remaining delay
|
|
46
|
+
freezes. `abort()` discards them.
|
|
47
|
+
|
|
48
|
+
## Counters
|
|
49
|
+
|
|
50
|
+
`started`, `succeeded`, and `failed` count attempts: a failure is counted even
|
|
51
|
+
when a later retry succeeds. `retried` increments only when another attempt is
|
|
52
|
+
actually scheduled.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# State and idle
|
|
2
|
+
|
|
3
|
+
Read this when reporting queue metrics, asserting on queue state in tests, or
|
|
4
|
+
waiting for work to finish.
|
|
5
|
+
|
|
6
|
+
## `getState()`
|
|
7
|
+
|
|
8
|
+
Returns a frozen snapshot; mutating it does not affect the queue.
|
|
9
|
+
|
|
10
|
+
| Field | Meaning |
|
|
11
|
+
| --- | --- |
|
|
12
|
+
| `rate` | Current starts per interval |
|
|
13
|
+
| `pending` | Queued items plus delayed retries |
|
|
14
|
+
| `active` | Callbacks currently executing |
|
|
15
|
+
| `state` | `"running"`, `"paused"`, `"stopped"`, `"aborted"`, or `"failed"` |
|
|
16
|
+
| `cooldownRemaining` | Ms until the cooldown ends, rounded up; `0` when none |
|
|
17
|
+
| `started`, `succeeded`, `failed` | Attempts; a retry is a new attempt |
|
|
18
|
+
| `retried` | Retries actually scheduled |
|
|
19
|
+
| `canceled` | Submitted items whose `result` `cancel()` rejected |
|
|
20
|
+
| `rateIncreases`, `rateDecreases` | Applied rate changes; match `onRateChange` calls exactly |
|
|
21
|
+
| `cooldowns`, `cooldownTotal` | `cooldownFor()` calls that moved the deadline later, and the ms they added |
|
|
22
|
+
|
|
23
|
+
Counters are monotonic and stop changing after `abort()` or a strategy failure.
|
|
24
|
+
A cancelled active attempt counts as neither succeeded nor failed.
|
|
25
|
+
|
|
26
|
+
## `waitForIdle()`
|
|
27
|
+
|
|
28
|
+
Resolves once pending work, active callbacks, and delayed retries are all
|
|
29
|
+
terminal; immediately if already idle.
|
|
30
|
+
|
|
31
|
+
- One-shot: a later enqueue does not affect a resolved promise. Concurrent
|
|
32
|
+
callers resolve together.
|
|
33
|
+
- A failure that schedules a retry never produces a transient idle.
|
|
34
|
+
- After `stop()`, waiters stay pending until a later enqueue lets the work
|
|
35
|
+
finish. Do not use `stop()` + `waitForIdle()` as shutdown.
|
|
36
|
+
- After `abort()`, waiters resolve as soon as no callback is active.
|
|
37
|
+
- While paused, waiters resolve only when pending and active are both `0`.
|
|
38
|
+
- On `failed`, every waiter rejects with the strategy error at once.
|