dynamic-throttled-queue 2.1.0 → 2.1.1-rc.6f2d27a

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 CHANGED
@@ -38,13 +38,15 @@ Set `concurrency` to bound callbacks that are still awaiting asynchronous comple
38
38
  | `max_rpi` | `number` | `min_rpi` | Maximum requests per interval |
39
39
  | `interval` | `number` | *required* | Milliseconds between each batch of requests |
40
40
  | `evenly_spaced` | `boolean` | `true` | Distribute requests evenly throughout the interval |
41
- | `errors_per_interval` | `number` | `5` | Error threshold per interval before adjusting rate |
41
+ | `errors_per_interval` | `number` | `5` | Positive integer error threshold per interval before adjusting rate |
42
42
  | `back_off` | `boolean` | `false` | Back off for 1 full interval when error threshold is hit |
43
- | `retry` | `number` | `0` | Number of times to retry failed callbacks |
43
+ | `retry` | `number` | `0` | Non-negative integer number of times to retry failed callbacks |
44
44
  | `concurrency` | `number` | — | Maximum callbacks awaiting asynchronous completion; omit for no limit |
45
- | `compact_threshold` | `number` | `512` | Minimum dead slots before internal queue compaction triggers |
45
+ | `compact_threshold` | `number` | `512` | Non-negative integer minimum dead slots before internal queue compaction triggers; `0` compacts at the earliest eligible point |
46
46
  | `onRateChange` | `(rate: number) => void` | — | Called when the current rate changes |
47
47
 
48
+ `retry`, `errors_per_interval`, and `compact_threshold` reject fractional and non-finite values. `errors_per_interval` must be at least `1`; `retry` and `compact_threshold` may be `0`.
49
+
48
50
  ## Handle API
49
51
 
50
52
  `createThrottledQueue` returns a function with additional properties:
@@ -63,6 +65,8 @@ console.log(throttle.pending); // number of queued callbacks
63
65
  throttle.stop(); // halt processing
64
66
  ```
65
67
 
68
+ `stop()` clears scheduler timers and retains callbacks that have not started. It cannot cancel an active asynchronous callback; if that callback later succeeds, returns `false`, or rejects, its settlement does not restart scheduling. A retry created by a failed active callback is retained with the pending work. Enqueueing another callback resumes the queue.
69
+
66
70
  ## Examples
67
71
 
68
72
  ### Basic
@@ -5,13 +5,14 @@ export type ThrottleOptions = {
5
5
  interval: number;
6
6
  max_rpi?: number;
7
7
  evenly_spaced?: boolean;
8
- /** Max errors per interval before rate decrease. Default 5. */
8
+ /** Positive integer error threshold per interval before rate decrease. Default 5. */
9
9
  errors_per_interval?: number;
10
10
  back_off?: boolean;
11
+ /** Non-negative integer retries for each failed callback. Default 0. */
11
12
  retry?: number;
12
13
  /** Maximum number of callbacks awaiting asynchronous settlement. Omit for no limit. */
13
14
  concurrency?: number;
14
- /** Minimum dead slots before queue compaction triggers. Default 512. */
15
+ /** Non-negative integer dead slots before queue compaction triggers. Default 512. */
15
16
  compact_threshold?: number;
16
17
  onRateChange?: (rate: number) => void;
17
18
  };
@@ -1,5 +1,7 @@
1
+ import { createRateController } from "./rate-controller.js";
2
+ import { createScheduler } from "./scheduler.js";
1
3
  export function createThrottledQueue(options) {
2
- const { min_rpi, interval, max_rpi = min_rpi, evenly_spaced = true, back_off = false, retry = 0, concurrency, compact_threshold = 512, onRateChange, } = options;
4
+ const { min_rpi, interval, max_rpi = min_rpi, concurrency, retry = 0, compact_threshold = 512 } = options;
3
5
  const errors_per_interval = options.errors_per_interval ?? 5;
4
6
  if (!Number.isInteger(min_rpi) || min_rpi < 1) {
5
7
  throw new Error("min_rpi must be a positive integer");
@@ -13,150 +15,20 @@ export function createThrottledQueue(options) {
13
15
  if (concurrency !== undefined && (!Number.isInteger(concurrency) || concurrency < 1)) {
14
16
  throw new Error("concurrency must be a positive integer");
15
17
  }
16
- let current_rpi = Math.ceil((max_rpi + min_rpi) / 2);
17
- let dyn_interval = evenly_spaced ? interval / current_rpi : interval;
18
- let dyn_requests_per_interval = evenly_spaced ? 1 : current_rpi;
19
- let error_count = 0;
20
- let skippedLast = false;
21
- let isRunning = false;
22
- let last_called = 0;
23
- let timeout;
24
- let dynTimeout;
25
- let active_count = 0;
26
- const max_concurrency = concurrency ?? Infinity;
27
- const queue = [];
28
- let head = 0;
29
- /** Halts timers. Retains unprocessed queue items; next enqueue resumes from where it left off. */
30
- function stop() {
31
- isRunning = false;
32
- skippedLast = false;
33
- clearTimeout(timeout);
34
- timeout = undefined;
35
- clearTimeout(dynTimeout);
36
- dynTimeout = undefined;
37
- if (head >= queue.length) {
38
- queue.length = 0;
39
- head = 0;
40
- }
18
+ if (!Number.isInteger(errors_per_interval) || errors_per_interval < 1) {
19
+ throw new Error("errors_per_interval must be a positive integer");
41
20
  }
42
- function handleResult(item, result) {
43
- if (result === false) {
44
- error_count++;
45
- if (item.retries > 0) {
46
- queue.push({ fn: item.fn, retries: item.retries - 1 });
47
- if (!isRunning && queue.length > head) {
48
- start();
49
- }
50
- }
51
- }
21
+ if (!Number.isInteger(retry) || retry < 0) {
22
+ throw new Error("retry must be a non-negative integer");
52
23
  }
53
- function handleSettlement(item, result, resume = false) {
54
- active_count--;
55
- handleResult(item, result);
56
- // A released slot can unblock pending work; dequeue still observes rate pacing.
57
- if (resume && isRunning && !skippedLast && queue.length > head) {
58
- dequeue();
59
- }
24
+ if (!Number.isInteger(compact_threshold) || compact_threshold < 0) {
25
+ throw new Error("compact_threshold must be a non-negative integer");
60
26
  }
61
- function dequeue() {
62
- const threshold = last_called + dyn_interval;
63
- const now = Date.now();
64
- if (now < threshold) {
65
- clearTimeout(timeout);
66
- timeout = setTimeout(dequeue, threshold - now);
67
- return;
68
- }
69
- // Retries appended by settlement are outside this batch and need another start opportunity.
70
- const end = Math.min(head + dyn_requests_per_interval, queue.length);
71
- let started = 0;
72
- while (head < end && active_count < max_concurrency) {
73
- const item = queue[head++];
74
- active_count++;
75
- if (started++ === 0) {
76
- last_called = Date.now();
77
- }
78
- let result;
79
- try {
80
- result = item.fn();
81
- }
82
- catch {
83
- handleSettlement(item, false);
84
- continue;
85
- }
86
- if (result instanceof Promise) {
87
- void result.then(value => handleSettlement(item, value, true), () => handleSettlement(item, false, true));
88
- }
89
- else {
90
- handleSettlement(item, result);
91
- }
92
- }
93
- // ponytail: splice is O(n), amortized by only firing when head > half array length
94
- if (head > compact_threshold && head > queue.length / 2) {
95
- queue.splice(0, head);
96
- head = 0;
97
- }
98
- if (head >= queue.length) {
99
- stop();
100
- return;
101
- }
102
- if (active_count >= max_concurrency) {
103
- return;
104
- }
105
- timeout = setTimeout(dequeue, dyn_interval);
106
- }
107
- function applyRate(newRpi) {
108
- if (newRpi === current_rpi)
109
- return;
110
- current_rpi = newRpi;
111
- onRateChange?.(current_rpi);
112
- if (evenly_spaced) {
113
- dyn_interval = interval / current_rpi;
114
- }
115
- else {
116
- dyn_requests_per_interval = current_rpi;
117
- }
118
- }
119
- // ponytail: async callbacks resolve after adjustRate fires — error_count may lag by one interval under async load
120
- function adjustRate() {
121
- dynTimeout = undefined;
122
- const wasSkipped = skippedLast;
123
- skippedLast = false;
124
- if (error_count >= errors_per_interval) {
125
- applyRate(Math.max(min_rpi, current_rpi - 1));
126
- if (isRunning && back_off && !wasSkipped) {
127
- clearTimeout(timeout);
128
- skippedLast = true;
129
- timeout = setTimeout(dequeue, dyn_interval + interval);
130
- }
131
- }
132
- else if (!wasSkipped && error_count === 0 && queue.length > head) {
133
- applyRate(Math.min(max_rpi, current_rpi + 1));
134
- }
135
- error_count = 0;
136
- if (isRunning) {
137
- dynTimeout = setTimeout(adjustRate, interval);
138
- }
139
- }
140
- function start() {
141
- if (skippedLast)
142
- return;
143
- isRunning = true;
144
- last_called = Date.now();
145
- clearTimeout(timeout);
146
- timeout = setTimeout(dequeue, dyn_interval);
147
- if (!dynTimeout) {
148
- dynTimeout = setTimeout(adjustRate, interval);
149
- }
150
- }
151
- function enqueue(callback) {
152
- queue.push({ fn: callback, retries: retry });
153
- if (!isRunning) {
154
- start();
155
- }
156
- }
157
- enqueue.stop = stop;
158
- // ponytail: defineProperty needed for getter; stop is plain assignment
159
- Object.defineProperty(enqueue, "pending", { get: () => queue.length - head });
160
- return enqueue;
27
+ return createScheduler(options, createRateController({
28
+ min_rpi,
29
+ max_rpi,
30
+ errors_per_interval,
31
+ back_off: options.back_off ?? false,
32
+ }));
161
33
  }
162
34
  //# sourceMappingURL=dynamic-throttled-queue.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic-throttled-queue.js","sourceRoot":"","sources":["../src/dynamic-throttled-queue.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dynamic-throttled-queue.js","sourceRoot":"","sources":["../src/dynamic-throttled-queue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,6BAA6B;AAC5D,OAAO,EAAE,eAAe,EAAE,uBAAuB;AA6BjD,MAAM,UAAU,oBAAoB,CAAC,OAAwB;IAC3D,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,EAAE,WAAW,EAAE,KAAK,GAAG,CAAC,EAAE,iBAAiB,GAAG,GAAG,EAAE,GAAG,OAAO,CAAC;IAE1G,MAAM,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,IAAI,CAAC,CAAC;IAE7D,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACxD,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,OAAO,GAAG,OAAO,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAC3D,CAAC;IACD,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACxD,CAAC;IAED,IAAI,WAAW,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI,WAAW,GAAG,CAAC,CAAC,EAAE,CAAC;QACrF,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IAC5D,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,mBAAmB,CAAC,IAAI,mBAAmB,GAAG,CAAC,EAAE,CAAC;QACtE,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;IACpE,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC1D,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,iBAAiB,CAAC,IAAI,iBAAiB,GAAG,CAAC,EAAE,CAAC;QAClE,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACtE,CAAC;IACD,OAAO,eAAe,CAAC,OAAO,EAAE,oBAAoB,CAAC;QACnD,OAAO;QACP,OAAO;QACP,mBAAmB;QACnB,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,KAAK;KACpC,CAAC,CAAC,CAAC;AACN,CAAC"}
@@ -0,0 +1,25 @@
1
+ type RateControllerOptions = {
2
+ min_rpi: number;
3
+ max_rpi: number;
4
+ errors_per_interval: number;
5
+ back_off: boolean;
6
+ };
7
+ type Observation = {
8
+ hasPendingWork: boolean;
9
+ wasSkipped: boolean;
10
+ };
11
+ export type RateDecision = {
12
+ rate: number;
13
+ shouldBackOff: boolean;
14
+ };
15
+ export type RateStrategy = (input: RateControllerOptions & Observation & {
16
+ rate: number;
17
+ errorCount: number;
18
+ }) => RateDecision;
19
+ export type RateController = {
20
+ readonly rate: number;
21
+ recordCompletion: (result: boolean | void) => void;
22
+ observe: (observation: Observation) => RateDecision;
23
+ };
24
+ export declare function createRateController(options: RateControllerOptions, strategy?: RateStrategy): RateController;
25
+ export {};
@@ -0,0 +1,32 @@
1
+ const linearRateStrategy = ({ min_rpi, max_rpi, errors_per_interval, back_off, rate, errorCount, hasPendingWork, wasSkipped, }) => {
2
+ if (errorCount >= errors_per_interval) {
3
+ return {
4
+ rate: Math.max(min_rpi, rate - 1),
5
+ shouldBackOff: back_off && !wasSkipped,
6
+ };
7
+ }
8
+ if (!wasSkipped && errorCount === 0 && hasPendingWork) {
9
+ return { rate: Math.min(max_rpi, rate + 1), shouldBackOff: false };
10
+ }
11
+ return { rate, shouldBackOff: false };
12
+ };
13
+ export function createRateController(options, strategy = linearRateStrategy) {
14
+ let rate = Math.ceil((options.max_rpi + options.min_rpi) / 2);
15
+ let errorCount = 0;
16
+ return {
17
+ get rate() {
18
+ return rate;
19
+ },
20
+ recordCompletion(result) {
21
+ if (result === false)
22
+ errorCount++;
23
+ },
24
+ observe(observation) {
25
+ const decision = strategy({ ...options, ...observation, rate, errorCount });
26
+ rate = Math.min(options.max_rpi, Math.max(options.min_rpi, decision.rate));
27
+ errorCount = 0;
28
+ return { ...decision, rate };
29
+ },
30
+ };
31
+ }
32
+ //# sourceMappingURL=rate-controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-controller.js","sourceRoot":"","sources":["../src/rate-controller.ts"],"names":[],"mappings":"AA4BA,MAAM,kBAAkB,GAAiB,CAAC,EACxC,OAAO,EACP,OAAO,EACP,mBAAmB,EACnB,QAAQ,EACR,IAAI,EACJ,UAAU,EACV,cAAc,EACd,UAAU,GACX,EAAE,EAAE;IACH,IAAI,UAAU,IAAI,mBAAmB,EAAE,CAAC;QACtC,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,GAAG,CAAC,CAAC;YACjC,aAAa,EAAE,QAAQ,IAAI,CAAC,UAAU;SACvC,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,UAAU,IAAI,UAAU,KAAK,CAAC,IAAI,cAAc,EAAE,CAAC;QACtD,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,GAAG,CAAC,CAAC,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;IACrE,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;AACxC,CAAC,CAAC;AAEF,MAAM,UAAU,oBAAoB,CAAC,OAA8B,EAAE,QAAQ,GAAG,kBAAkB;IAChG,IAAI,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9D,IAAI,UAAU,GAAG,CAAC,CAAC;IAEnB,OAAO;QACL,IAAI,IAAI;YACN,OAAO,IAAI,CAAC;QACd,CAAC;QACD,gBAAgB,CAAC,MAAsB;YACrC,IAAI,MAAM,KAAK,KAAK;gBAAE,UAAU,EAAE,CAAC;QACrC,CAAC;QACD,OAAO,CAAC,WAAwB;YAC9B,MAAM,QAAQ,GAAG,QAAQ,CAAC,EAAE,GAAG,OAAO,EAAE,GAAG,WAAW,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;YAC5E,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;YAC3E,UAAU,GAAG,CAAC,CAAC;YACf,OAAO,EAAE,GAAG,QAAQ,EAAE,IAAI,EAAE,CAAC;QAC/B,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { ThrottleHandle, ThrottleOptions } from "./dynamic-throttled-queue.js";
2
+ import type { RateController } from "./rate-controller.js";
3
+ export declare function createScheduler(options: ThrottleOptions, rateController: RateController): ThrottleHandle;
@@ -0,0 +1,132 @@
1
+ export function createScheduler(options, rateController) {
2
+ const { interval, evenly_spaced = true, retry = 0, concurrency, compact_threshold = 512, onRateChange, } = options;
3
+ let current_rpi = rateController.rate;
4
+ let dyn_interval = evenly_spaced ? interval / current_rpi : interval;
5
+ let dyn_requests_per_interval = evenly_spaced ? 1 : current_rpi;
6
+ let skippedLast = false;
7
+ let isRunning = false;
8
+ let last_called = 0;
9
+ let timeout;
10
+ let dynTimeout;
11
+ let active_count = 0;
12
+ let isStopped = false;
13
+ const max_concurrency = concurrency ?? Infinity;
14
+ const queue = [];
15
+ let head = 0;
16
+ function halt() {
17
+ isRunning = false;
18
+ skippedLast = false;
19
+ clearTimeout(timeout);
20
+ timeout = undefined;
21
+ clearTimeout(dynTimeout);
22
+ dynTimeout = undefined;
23
+ if (head >= queue.length) {
24
+ queue.length = 0;
25
+ head = 0;
26
+ }
27
+ }
28
+ function stop() {
29
+ isStopped = true;
30
+ halt();
31
+ }
32
+ function handleResult(item, result) {
33
+ rateController.recordCompletion(result);
34
+ if (result === false && item.retries > 0) {
35
+ queue.push({ fn: item.fn, retries: item.retries - 1 });
36
+ if (!isRunning && !isStopped && queue.length > head)
37
+ start();
38
+ }
39
+ }
40
+ function handleSettlement(item, result, resume = false) {
41
+ active_count--;
42
+ handleResult(item, result);
43
+ if (resume && isRunning && !skippedLast && queue.length > head)
44
+ dequeue();
45
+ }
46
+ function dequeue() {
47
+ const threshold = last_called + dyn_interval;
48
+ const now = Date.now();
49
+ if (now < threshold) {
50
+ clearTimeout(timeout);
51
+ timeout = setTimeout(dequeue, threshold - now);
52
+ return;
53
+ }
54
+ const end = Math.min(head + dyn_requests_per_interval, queue.length);
55
+ let started = 0;
56
+ while (head < end && active_count < max_concurrency) {
57
+ const item = queue[head++];
58
+ active_count++;
59
+ if (started++ === 0)
60
+ last_called = Date.now();
61
+ let result;
62
+ try {
63
+ result = item.fn();
64
+ }
65
+ catch {
66
+ handleSettlement(item, false);
67
+ continue;
68
+ }
69
+ if (result instanceof Promise) {
70
+ void result.then(value => handleSettlement(item, value, true), () => handleSettlement(item, false, true));
71
+ }
72
+ else {
73
+ handleSettlement(item, result);
74
+ }
75
+ }
76
+ if (head > compact_threshold && head > queue.length / 2) {
77
+ queue.splice(0, head);
78
+ head = 0;
79
+ }
80
+ if (head >= queue.length) {
81
+ halt();
82
+ return;
83
+ }
84
+ if (active_count >= max_concurrency)
85
+ return;
86
+ timeout = setTimeout(dequeue, dyn_interval);
87
+ }
88
+ function applyRate(newRpi) {
89
+ if (newRpi === current_rpi)
90
+ return;
91
+ current_rpi = newRpi;
92
+ onRateChange?.(current_rpi);
93
+ if (evenly_spaced)
94
+ dyn_interval = interval / current_rpi;
95
+ else
96
+ dyn_requests_per_interval = current_rpi;
97
+ }
98
+ function adjustRate() {
99
+ dynTimeout = undefined;
100
+ const wasSkipped = skippedLast;
101
+ skippedLast = false;
102
+ const decision = rateController.observe({ hasPendingWork: queue.length > head, wasSkipped });
103
+ applyRate(decision.rate);
104
+ if (decision.shouldBackOff) {
105
+ clearTimeout(timeout);
106
+ skippedLast = true;
107
+ timeout = setTimeout(dequeue, dyn_interval + interval);
108
+ }
109
+ if (isRunning)
110
+ dynTimeout = setTimeout(adjustRate, interval);
111
+ }
112
+ function start() {
113
+ if (skippedLast)
114
+ return;
115
+ isRunning = true;
116
+ last_called = Date.now();
117
+ clearTimeout(timeout);
118
+ timeout = setTimeout(dequeue, dyn_interval);
119
+ if (!dynTimeout)
120
+ dynTimeout = setTimeout(adjustRate, interval);
121
+ }
122
+ function enqueue(callback) {
123
+ queue.push({ fn: callback, retries: retry });
124
+ isStopped = false;
125
+ if (!isRunning)
126
+ start();
127
+ }
128
+ enqueue.stop = stop;
129
+ Object.defineProperty(enqueue, "pending", { get: () => queue.length - head });
130
+ return enqueue;
131
+ }
132
+ //# sourceMappingURL=scheduler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dynamic-throttled-queue",
3
- "version": "2.1.0",
3
+ "version": "2.1.1-rc.6f2d27a",
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": [