@naturalcycles/nodejs-lib 15.21.0 → 15.22.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.
@@ -7,6 +7,7 @@ export * from './ndjson/transformJsonParse.js';
7
7
  export * from './ndjson/transformToNDJson.js';
8
8
  export * from './pipeline/pipeline.js';
9
9
  export * from './progressLogger.js';
10
+ export * from './readable/readableCombined.js';
10
11
  export * from './readable/readableCreate.js';
11
12
  export * from './readable/readableForEach.js';
12
13
  export * from './readable/readableFromArray.js';
@@ -7,6 +7,7 @@ export * from './ndjson/transformJsonParse.js';
7
7
  export * from './ndjson/transformToNDJson.js';
8
8
  export * from './pipeline/pipeline.js';
9
9
  export * from './progressLogger.js';
10
+ export * from './readable/readableCombined.js';
10
11
  export * from './readable/readableCreate.js';
11
12
  export * from './readable/readableForEach.js';
12
13
  export * from './readable/readableFromArray.js';
@@ -19,7 +19,6 @@ export function transformJsonParse(opt = {}) {
19
19
  writableObjectMode: false,
20
20
  readableObjectMode: true,
21
21
  // highWatermark increased, because it's proven to be faster: https://github.com/nodejs/node/pull/52037
22
- // todo: it'll be default in Node 22, then we can remove this
23
22
  writableHighWaterMark: 64 * 1024,
24
23
  transform(chunk, _, cb) {
25
24
  try {
@@ -0,0 +1,30 @@
1
+ import { Readable } from 'node:stream';
2
+ import type { ReadableTyped } from '@naturalcycles/nodejs-lib/stream';
3
+ /**
4
+ * Allows to combine multiple Readables into 1 Readable.
5
+ * As soon as any of the input Readables emit - the output Readable emits
6
+ * (passes through).
7
+ * Order is not preserved in any way, first come first served!
8
+ *
9
+ * Readable completes when all input Readables complete.
10
+ *
11
+ * @experimental
12
+ */
13
+ export declare class ReadableCombined<T> extends Readable implements ReadableTyped<T> {
14
+ inputs: Readable[];
15
+ static create<T>(inputs: Readable[]): ReadableCombined<T>;
16
+ private constructor();
17
+ /**
18
+ * If defined - we are in Paused mode
19
+ * and should await the lock to be resolved before proceeding.
20
+ *
21
+ * If not defined - we are in Flowing mode, no limits in data flow.
22
+ */
23
+ private lock?;
24
+ private countIn;
25
+ private countOut;
26
+ private countReads;
27
+ private start;
28
+ _read(): void;
29
+ private logStats;
30
+ }
@@ -0,0 +1,77 @@
1
+ import { Readable } from 'node:stream';
2
+ import { pDefer } from '@naturalcycles/js-lib/promise/pDefer.js';
3
+ import { pMap } from '@naturalcycles/js-lib/promise/pMap.js';
4
+ /**
5
+ * Allows to combine multiple Readables into 1 Readable.
6
+ * As soon as any of the input Readables emit - the output Readable emits
7
+ * (passes through).
8
+ * Order is not preserved in any way, first come first served!
9
+ *
10
+ * Readable completes when all input Readables complete.
11
+ *
12
+ * @experimental
13
+ */
14
+ export class ReadableCombined extends Readable {
15
+ inputs;
16
+ static create(inputs) {
17
+ return new ReadableCombined(inputs);
18
+ }
19
+ constructor(inputs) {
20
+ super({ objectMode: true });
21
+ this.inputs = inputs;
22
+ void this.start();
23
+ }
24
+ /**
25
+ * If defined - we are in Paused mode
26
+ * and should await the lock to be resolved before proceeding.
27
+ *
28
+ * If not defined - we are in Flowing mode, no limits in data flow.
29
+ */
30
+ lock;
31
+ // biome-ignore lint/correctness/noUnusedPrivateClassMembers: ok
32
+ countIn = 0;
33
+ // biome-ignore lint/correctness/noUnusedPrivateClassMembers: ok
34
+ countOut = 0;
35
+ // biome-ignore lint/correctness/noUnusedPrivateClassMembers: ok
36
+ countReads = 0;
37
+ async start() {
38
+ await pMap(this.inputs, async (input, i) => {
39
+ for await (const item of input) {
40
+ this.countIn++;
41
+ this.logStats();
42
+ if (this.lock) {
43
+ await this.lock;
44
+ // lock is undefined at this point
45
+ }
46
+ const shouldContinue = this.push(item);
47
+ this.countOut++;
48
+ if (!shouldContinue && !this.lock) {
49
+ this.lock = pDefer();
50
+ console.log(`ReadableCombined.push #${i} returned false, pausing the flow!`);
51
+ }
52
+ }
53
+ console.log(`ReadableCombined: input #${i} done`);
54
+ });
55
+ console.log(`ReadableCombined: all inputs done!`);
56
+ this.push(null);
57
+ }
58
+ _read() {
59
+ this.countReads++;
60
+ if (this.lock) {
61
+ console.log(`ReadableCombined._read: resuming the flow!`);
62
+ // calling it in this order is important!
63
+ // this.lock should be undefined BEFORE we call lock.resolve()
64
+ const { lock } = this;
65
+ this.lock = undefined;
66
+ lock.resolve();
67
+ }
68
+ }
69
+ logStats() {
70
+ const { countIn, countOut, countReads } = this;
71
+ console.log({
72
+ countIn,
73
+ countOut,
74
+ countReads,
75
+ });
76
+ }
77
+ }
@@ -1,6 +1,6 @@
1
1
  import { ErrorMode } from '@naturalcycles/js-lib/error';
2
2
  import type { CommonLogger } from '@naturalcycles/js-lib/log';
3
- import { type AbortableAsyncMapper, type AsyncPredicate, END, type Promisable, SKIP, type StringMap, type UnixTimestampMillis } from '@naturalcycles/js-lib/types';
3
+ import { type AbortableAsyncMapper, type AsyncPredicate, END, type PositiveInteger, type Promisable, SKIP, type StringMap, type UnixTimestampMillis } from '@naturalcycles/js-lib/types';
4
4
  import type { TransformTyped } from '../stream.model.js';
5
5
  export interface TransformMapOptions<IN = any, OUT = IN> {
6
6
  /**
@@ -21,7 +21,15 @@ export interface TransformMapOptions<IN = any, OUT = IN> {
21
21
  * UPD: changed back from 32 to 16, "to be on a safe side", as 32 sometimes
22
22
  * causes "Datastore timeout errors".
23
23
  */
24
- concurrency?: number;
24
+ concurrency?: PositiveInteger;
25
+ /**
26
+ * Defaults to 64 items.
27
+ * (objectMode default is 16, but we increased it)
28
+ *
29
+ * Affects both readable and writable highWaterMark (buffer).
30
+ * So, 64 means a total buffer of 128 (64 input and 64 output buffer).
31
+ */
32
+ highWaterMark?: PositiveInteger;
25
33
  /**
26
34
  * @default THROW_IMMEDIATELY
27
35
  */
@@ -21,7 +21,7 @@ import { pipelineClose } from '../stream.util.js';
21
21
  * If an Array is returned by `mapper` - it will be flattened and multiple results will be emitted from it. Tested by Array.isArray().
22
22
  */
23
23
  export function transformMap(mapper, opt = {}) {
24
- const { concurrency = 16, predicate, // we now default to "no predicate" (meaning pass-everything)
24
+ const { concurrency = 16, highWaterMark = 64, predicate, // we now default to "no predicate" (meaning pass-everything)
25
25
  errorMode = ErrorMode.THROW_IMMEDIATELY, onError, onDone, metric = 'stream', logger = console, } = opt;
26
26
  const started = Date.now();
27
27
  let index = -1;
@@ -31,6 +31,8 @@ export function transformMap(mapper, opt = {}) {
31
31
  const collectedErrors = []; // only used if errorMode == THROW_AGGREGATED
32
32
  return through2Concurrent.obj({
33
33
  maxConcurrency: concurrency,
34
+ readableHighWaterMark: highWaterMark,
35
+ writableHighWaterMark: highWaterMark,
34
36
  async final(cb) {
35
37
  // console.log('transformMap final')
36
38
  logErrorStats(true);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@naturalcycles/nodejs-lib",
3
3
  "type": "module",
4
- "version": "15.21.0",
4
+ "version": "15.22.0",
5
5
  "dependencies": {
6
6
  "@naturalcycles/js-lib": "^15",
7
7
  "@types/js-yaml": "^4",
@@ -7,6 +7,7 @@ export * from './ndjson/transformJsonParse.js'
7
7
  export * from './ndjson/transformToNDJson.js'
8
8
  export * from './pipeline/pipeline.js'
9
9
  export * from './progressLogger.js'
10
+ export * from './readable/readableCombined.js'
10
11
  export * from './readable/readableCreate.js'
11
12
  export * from './readable/readableForEach.js'
12
13
  export * from './readable/readableFromArray.js'
@@ -36,7 +36,6 @@ export function transformJsonParse<ROW = any>(
36
36
  writableObjectMode: false,
37
37
  readableObjectMode: true,
38
38
  // highWatermark increased, because it's proven to be faster: https://github.com/nodejs/node/pull/52037
39
- // todo: it'll be default in Node 22, then we can remove this
40
39
  writableHighWaterMark: 64 * 1024,
41
40
  transform(chunk: string, _, cb) {
42
41
  try {
@@ -0,0 +1,87 @@
1
+ import { Readable } from 'node:stream'
2
+ import { type DeferredPromise, pDefer } from '@naturalcycles/js-lib/promise/pDefer.js'
3
+ import { pMap } from '@naturalcycles/js-lib/promise/pMap.js'
4
+ import type { ReadableTyped } from '@naturalcycles/nodejs-lib/stream'
5
+
6
+ /**
7
+ * Allows to combine multiple Readables into 1 Readable.
8
+ * As soon as any of the input Readables emit - the output Readable emits
9
+ * (passes through).
10
+ * Order is not preserved in any way, first come first served!
11
+ *
12
+ * Readable completes when all input Readables complete.
13
+ *
14
+ * @experimental
15
+ */
16
+ export class ReadableCombined<T> extends Readable implements ReadableTyped<T> {
17
+ static create<T>(inputs: Readable[]): ReadableCombined<T> {
18
+ return new ReadableCombined<T>(inputs)
19
+ }
20
+
21
+ private constructor(public inputs: Readable[]) {
22
+ super({ objectMode: true })
23
+ void this.start()
24
+ }
25
+
26
+ /**
27
+ * If defined - we are in Paused mode
28
+ * and should await the lock to be resolved before proceeding.
29
+ *
30
+ * If not defined - we are in Flowing mode, no limits in data flow.
31
+ */
32
+ private lock?: DeferredPromise
33
+
34
+ // biome-ignore lint/correctness/noUnusedPrivateClassMembers: ok
35
+ private countIn = 0
36
+ // biome-ignore lint/correctness/noUnusedPrivateClassMembers: ok
37
+ private countOut = 0
38
+ // biome-ignore lint/correctness/noUnusedPrivateClassMembers: ok
39
+ private countReads = 0
40
+
41
+ private async start(): Promise<void> {
42
+ await pMap(this.inputs, async (input, i) => {
43
+ for await (const item of input) {
44
+ this.countIn++
45
+ this.logStats()
46
+ if (this.lock) {
47
+ await this.lock
48
+ // lock is undefined at this point
49
+ }
50
+
51
+ const shouldContinue = this.push(item)
52
+ this.countOut++
53
+ if (!shouldContinue && !this.lock) {
54
+ this.lock = pDefer()
55
+ console.log(`ReadableCombined.push #${i} returned false, pausing the flow!`)
56
+ }
57
+ }
58
+
59
+ console.log(`ReadableCombined: input #${i} done`)
60
+ })
61
+
62
+ console.log(`ReadableCombined: all inputs done!`)
63
+ this.push(null)
64
+ }
65
+
66
+ override _read(): void {
67
+ this.countReads++
68
+
69
+ if (this.lock) {
70
+ console.log(`ReadableCombined._read: resuming the flow!`)
71
+ // calling it in this order is important!
72
+ // this.lock should be undefined BEFORE we call lock.resolve()
73
+ const { lock } = this
74
+ this.lock = undefined
75
+ lock.resolve()
76
+ }
77
+ }
78
+
79
+ private logStats(): void {
80
+ const { countIn, countOut, countReads } = this
81
+ console.log({
82
+ countIn,
83
+ countOut,
84
+ countReads,
85
+ })
86
+ }
87
+ }
@@ -7,6 +7,7 @@ import {
7
7
  type AbortableAsyncMapper,
8
8
  type AsyncPredicate,
9
9
  END,
10
+ type PositiveInteger,
10
11
  type Promisable,
11
12
  SKIP,
12
13
  type StringMap,
@@ -38,7 +39,16 @@ export interface TransformMapOptions<IN = any, OUT = IN> {
38
39
  * UPD: changed back from 32 to 16, "to be on a safe side", as 32 sometimes
39
40
  * causes "Datastore timeout errors".
40
41
  */
41
- concurrency?: number
42
+ concurrency?: PositiveInteger
43
+
44
+ /**
45
+ * Defaults to 64 items.
46
+ * (objectMode default is 16, but we increased it)
47
+ *
48
+ * Affects both readable and writable highWaterMark (buffer).
49
+ * So, 64 means a total buffer of 128 (64 input and 64 output buffer).
50
+ */
51
+ highWaterMark?: PositiveInteger
42
52
 
43
53
  /**
44
54
  * @default THROW_IMMEDIATELY
@@ -123,6 +133,7 @@ export function transformMap<IN = any, OUT = IN>(
123
133
  ): TransformTyped<IN, OUT> {
124
134
  const {
125
135
  concurrency = 16,
136
+ highWaterMark = 64,
126
137
  predicate, // we now default to "no predicate" (meaning pass-everything)
127
138
  errorMode = ErrorMode.THROW_IMMEDIATELY,
128
139
  onError,
@@ -141,6 +152,8 @@ export function transformMap<IN = any, OUT = IN>(
141
152
  return through2Concurrent.obj(
142
153
  {
143
154
  maxConcurrency: concurrency,
155
+ readableHighWaterMark: highWaterMark,
156
+ writableHighWaterMark: highWaterMark,
144
157
  async final(cb) {
145
158
  // console.log('transformMap final')
146
159