@naturalcycles/nodejs-lib 13.28.1 → 13.29.0
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/dist/index.d.ts
CHANGED
|
@@ -42,6 +42,7 @@ export * from './stream/transform/transformSplit';
|
|
|
42
42
|
export * from './stream/transform/transformTap';
|
|
43
43
|
export * from './stream/transform/transformToArray';
|
|
44
44
|
export * from './stream/transform/transformTee';
|
|
45
|
+
export * from './stream/transform/transformThrottle';
|
|
45
46
|
export * from './stream/transform/worker/baseWorkerClass';
|
|
46
47
|
export * from './stream/transform/worker/transformMultiThreaded';
|
|
47
48
|
export * from './stream/transform/worker/transformMultiThreaded.model';
|
package/dist/index.js
CHANGED
|
@@ -46,6 +46,7 @@ tslib_1.__exportStar(require("./stream/transform/transformSplit"), exports);
|
|
|
46
46
|
tslib_1.__exportStar(require("./stream/transform/transformTap"), exports);
|
|
47
47
|
tslib_1.__exportStar(require("./stream/transform/transformToArray"), exports);
|
|
48
48
|
tslib_1.__exportStar(require("./stream/transform/transformTee"), exports);
|
|
49
|
+
tslib_1.__exportStar(require("./stream/transform/transformThrottle"), exports);
|
|
49
50
|
tslib_1.__exportStar(require("./stream/transform/worker/baseWorkerClass"), exports);
|
|
50
51
|
tslib_1.__exportStar(require("./stream/transform/worker/transformMultiThreaded"), exports);
|
|
51
52
|
tslib_1.__exportStar(require("./stream/transform/worker/transformMultiThreaded.model"), exports);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { NumberOfSeconds, PositiveInteger } from '@naturalcycles/js-lib';
|
|
2
|
+
import { TransformTyped } from '../stream.model';
|
|
3
|
+
export interface TransformThrottleOptions {
|
|
4
|
+
/**
|
|
5
|
+
* How many items to allow per `interval` of seconds.
|
|
6
|
+
*/
|
|
7
|
+
throughput: PositiveInteger;
|
|
8
|
+
/**
|
|
9
|
+
* How long is the interval (in seconds) where number of items should not exceed `throughput`.
|
|
10
|
+
*/
|
|
11
|
+
interval: NumberOfSeconds;
|
|
12
|
+
debug?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Allows to throttle the throughput of the stream.
|
|
16
|
+
* For example, when you have an API with rate limit of 5000 requests per minute,
|
|
17
|
+
* `transformThrottle` can help you utilize it most efficiently.
|
|
18
|
+
* You can define it as:
|
|
19
|
+
*
|
|
20
|
+
* _pipeline([
|
|
21
|
+
* // ...
|
|
22
|
+
* transformThrottle({
|
|
23
|
+
* throughput: 5000,
|
|
24
|
+
* interval: 60,
|
|
25
|
+
* }),
|
|
26
|
+
* // ...
|
|
27
|
+
* ])
|
|
28
|
+
*
|
|
29
|
+
* @experimental
|
|
30
|
+
*/
|
|
31
|
+
export declare function transformThrottle<T>(opt: TransformThrottleOptions): TransformTyped<T, T>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.transformThrottle = transformThrottle;
|
|
4
|
+
const node_stream_1 = require("node:stream");
|
|
5
|
+
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
6
|
+
/**
|
|
7
|
+
* Allows to throttle the throughput of the stream.
|
|
8
|
+
* For example, when you have an API with rate limit of 5000 requests per minute,
|
|
9
|
+
* `transformThrottle` can help you utilize it most efficiently.
|
|
10
|
+
* You can define it as:
|
|
11
|
+
*
|
|
12
|
+
* _pipeline([
|
|
13
|
+
* // ...
|
|
14
|
+
* transformThrottle({
|
|
15
|
+
* throughput: 5000,
|
|
16
|
+
* interval: 60,
|
|
17
|
+
* }),
|
|
18
|
+
* // ...
|
|
19
|
+
* ])
|
|
20
|
+
*
|
|
21
|
+
* @experimental
|
|
22
|
+
*/
|
|
23
|
+
function transformThrottle(opt) {
|
|
24
|
+
const { throughput, interval, debug } = opt;
|
|
25
|
+
let count = 0;
|
|
26
|
+
let start;
|
|
27
|
+
let paused;
|
|
28
|
+
let timeout;
|
|
29
|
+
return new node_stream_1.Transform({
|
|
30
|
+
objectMode: true,
|
|
31
|
+
async transform(item, _, cb) {
|
|
32
|
+
// console.log('incoming', item, { paused: !!paused, count })
|
|
33
|
+
if (!start) {
|
|
34
|
+
start = Date.now();
|
|
35
|
+
timeout = setTimeout(() => onInterval(this), interval * 1000);
|
|
36
|
+
if (debug) {
|
|
37
|
+
console.log(`${js_lib_1.localTime.now().toPretty()} transformThrottle started with`, {
|
|
38
|
+
throughput,
|
|
39
|
+
interval,
|
|
40
|
+
rps: Math.round(throughput / interval),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (paused) {
|
|
45
|
+
// console.log('awaiting pause', {item, count})
|
|
46
|
+
await paused;
|
|
47
|
+
}
|
|
48
|
+
if (++count >= throughput) {
|
|
49
|
+
// console.log('pausing now after', {item, count})
|
|
50
|
+
paused = (0, js_lib_1.pDefer)();
|
|
51
|
+
if (debug) {
|
|
52
|
+
console.log(`${js_lib_1.localTime.now().toPretty()} transformThrottle activated: ${count} items passed in ${(0, js_lib_1._ms)(interval * 1000)}, will pause for ${(0, js_lib_1._ms)(interval * 1000 - (Date.now() - start))}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
cb(null, item); // pass the item through
|
|
56
|
+
},
|
|
57
|
+
final(cb) {
|
|
58
|
+
clearTimeout(timeout);
|
|
59
|
+
cb();
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
function onInterval(transform) {
|
|
63
|
+
if (!paused)
|
|
64
|
+
return;
|
|
65
|
+
if (debug) {
|
|
66
|
+
console.log(`${js_lib_1.localTime.now().toPretty()} transformThrottle resumed`);
|
|
67
|
+
}
|
|
68
|
+
count = 0;
|
|
69
|
+
start = Date.now();
|
|
70
|
+
timeout = setTimeout(() => onInterval(transform), interval * 1000);
|
|
71
|
+
paused.resolve();
|
|
72
|
+
paused = undefined;
|
|
73
|
+
}
|
|
74
|
+
}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -52,6 +52,7 @@ export * from './stream/transform/transformSplit'
|
|
|
52
52
|
export * from './stream/transform/transformTap'
|
|
53
53
|
export * from './stream/transform/transformToArray'
|
|
54
54
|
export * from './stream/transform/transformTee'
|
|
55
|
+
export * from './stream/transform/transformThrottle'
|
|
55
56
|
export * from './stream/transform/worker/baseWorkerClass'
|
|
56
57
|
export * from './stream/transform/worker/transformMultiThreaded'
|
|
57
58
|
export * from './stream/transform/worker/transformMultiThreaded.model'
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Transform } from 'node:stream'
|
|
2
|
+
import {
|
|
3
|
+
_ms,
|
|
4
|
+
DeferredPromise,
|
|
5
|
+
localTime,
|
|
6
|
+
NumberOfMilliseconds,
|
|
7
|
+
NumberOfSeconds,
|
|
8
|
+
pDefer,
|
|
9
|
+
PositiveInteger,
|
|
10
|
+
} from '@naturalcycles/js-lib'
|
|
11
|
+
import { TransformTyped } from '../stream.model'
|
|
12
|
+
|
|
13
|
+
export interface TransformThrottleOptions {
|
|
14
|
+
/**
|
|
15
|
+
* How many items to allow per `interval` of seconds.
|
|
16
|
+
*/
|
|
17
|
+
throughput: PositiveInteger
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* How long is the interval (in seconds) where number of items should not exceed `throughput`.
|
|
21
|
+
*/
|
|
22
|
+
interval: NumberOfSeconds
|
|
23
|
+
|
|
24
|
+
debug?: boolean
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Allows to throttle the throughput of the stream.
|
|
29
|
+
* For example, when you have an API with rate limit of 5000 requests per minute,
|
|
30
|
+
* `transformThrottle` can help you utilize it most efficiently.
|
|
31
|
+
* You can define it as:
|
|
32
|
+
*
|
|
33
|
+
* _pipeline([
|
|
34
|
+
* // ...
|
|
35
|
+
* transformThrottle({
|
|
36
|
+
* throughput: 5000,
|
|
37
|
+
* interval: 60,
|
|
38
|
+
* }),
|
|
39
|
+
* // ...
|
|
40
|
+
* ])
|
|
41
|
+
*
|
|
42
|
+
* @experimental
|
|
43
|
+
*/
|
|
44
|
+
export function transformThrottle<T>(opt: TransformThrottleOptions): TransformTyped<T, T> {
|
|
45
|
+
const { throughput, interval, debug } = opt
|
|
46
|
+
|
|
47
|
+
let count = 0
|
|
48
|
+
let start: NumberOfMilliseconds
|
|
49
|
+
let paused: DeferredPromise | undefined
|
|
50
|
+
let timeout: NodeJS.Timeout | undefined
|
|
51
|
+
|
|
52
|
+
return new Transform({
|
|
53
|
+
objectMode: true,
|
|
54
|
+
async transform(item: T, _, cb) {
|
|
55
|
+
// console.log('incoming', item, { paused: !!paused, count })
|
|
56
|
+
if (!start) {
|
|
57
|
+
start = Date.now()
|
|
58
|
+
timeout = setTimeout(() => onInterval(this), interval * 1000)
|
|
59
|
+
if (debug) {
|
|
60
|
+
console.log(`${localTime.now().toPretty()} transformThrottle started with`, {
|
|
61
|
+
throughput,
|
|
62
|
+
interval,
|
|
63
|
+
rps: Math.round(throughput / interval),
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (paused) {
|
|
69
|
+
// console.log('awaiting pause', {item, count})
|
|
70
|
+
await paused
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (++count >= throughput) {
|
|
74
|
+
// console.log('pausing now after', {item, count})
|
|
75
|
+
paused = pDefer()
|
|
76
|
+
if (debug) {
|
|
77
|
+
console.log(
|
|
78
|
+
`${localTime.now().toPretty()} transformThrottle activated: ${count} items passed in ${_ms(interval * 1000)}, will pause for ${_ms(interval * 1000 - (Date.now() - start))}`,
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
cb(null, item) // pass the item through
|
|
84
|
+
},
|
|
85
|
+
final(cb) {
|
|
86
|
+
clearTimeout(timeout)
|
|
87
|
+
cb()
|
|
88
|
+
},
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
function onInterval(transform: Transform): void {
|
|
92
|
+
if (!paused) return
|
|
93
|
+
|
|
94
|
+
if (debug) {
|
|
95
|
+
console.log(`${localTime.now().toPretty()} transformThrottle resumed`)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
count = 0
|
|
99
|
+
start = Date.now()
|
|
100
|
+
timeout = setTimeout(() => onInterval(transform), interval * 1000)
|
|
101
|
+
paused.resolve()
|
|
102
|
+
paused = undefined
|
|
103
|
+
}
|
|
104
|
+
}
|