@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/nodejs-lib",
3
- "version": "13.28.1",
3
+ "version": "13.29.0",
4
4
  "scripts": {
5
5
  "prepare": "husky",
6
6
  "docs-serve": "vuepress dev docs",
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
+ }