@naturalcycles/nodejs-lib 15.27.3 → 15.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/stream/index.d.ts +1 -1
- package/dist/stream/index.js +1 -1
- package/dist/stream/pipeline.d.ts +13 -7
- package/dist/stream/pipeline.js +27 -5
- package/dist/stream/readable/{readableCreate.d.ts → createReadable.d.ts} +9 -3
- package/dist/stream/readable/{readableCreate.js → createReadable.js} +23 -2
- package/dist/stream/stream.model.d.ts +4 -14
- package/dist/stream/transform/transformFilter.d.ts +1 -1
- package/dist/stream/transform/transformFilter.js +2 -2
- package/dist/stream/transform/transformFork.js +4 -4
- package/dist/stream/transform/transformLogProgress.js +1 -1
- package/dist/stream/transform/transformMap.d.ts +3 -2
- package/dist/stream/transform/transformMap.js +15 -3
- package/dist/stream/transform/transformTap.d.ts +6 -6
- package/dist/stream/transform/transformTap.js +24 -4
- package/dist/stream/writable/writableVoid.js +3 -2
- package/package.json +1 -1
- package/src/stream/index.ts +1 -1
- package/src/stream/pipeline.ts +42 -18
- package/src/stream/readable/{readableCreate.ts → createReadable.ts} +26 -3
- package/src/stream/stream.model.ts +7 -15
- package/src/stream/transform/transformFilter.ts +2 -2
- package/src/stream/transform/transformFork.ts +4 -4
- package/src/stream/transform/transformLogProgress.ts +1 -1
- package/src/stream/transform/transformMap.ts +17 -3
- package/src/stream/transform/transformTap.ts +31 -12
- package/src/stream/writable/writableVoid.ts +3 -2
package/dist/stream/index.d.ts
CHANGED
|
@@ -5,8 +5,8 @@ export * from './ndjson/transformJsonParse.js';
|
|
|
5
5
|
export * from './ndjson/transformToNDJson.js';
|
|
6
6
|
export * from './pipeline.js';
|
|
7
7
|
export * from './progressLogger.js';
|
|
8
|
+
export * from './readable/createReadable.js';
|
|
8
9
|
export * from './readable/readableCombined.js';
|
|
9
|
-
export * from './readable/readableCreate.js';
|
|
10
10
|
export * from './readable/readableFromArray.js';
|
|
11
11
|
export * from './stream.model.js';
|
|
12
12
|
export * from './transform/transformChunk.js';
|
package/dist/stream/index.js
CHANGED
|
@@ -5,8 +5,8 @@ export * from './ndjson/transformJsonParse.js';
|
|
|
5
5
|
export * from './ndjson/transformToNDJson.js';
|
|
6
6
|
export * from './pipeline.js';
|
|
7
7
|
export * from './progressLogger.js';
|
|
8
|
+
export * from './readable/createReadable.js';
|
|
8
9
|
export * from './readable/readableCombined.js';
|
|
9
|
-
export * from './readable/readableCreate.js';
|
|
10
10
|
export * from './readable/readableFromArray.js';
|
|
11
11
|
export * from './stream.model.js';
|
|
12
12
|
export * from './transform/transformChunk.js';
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { type Transform } from 'node:stream';
|
|
2
2
|
import type { ReadableStream as WebReadableStream } from 'node:stream/web';
|
|
3
3
|
import { type ZlibOptions } from 'node:zlib';
|
|
4
|
-
import type
|
|
4
|
+
import { type AbortableAsyncMapper, type AsyncIndexedMapper, type AsyncPredicate, type END, type IndexedMapper, type NonNegativeInteger, type PositiveInteger, type Predicate, type SKIP } from '@naturalcycles/js-lib/types';
|
|
5
5
|
import type { ReadableTyped, TransformOptions, TransformTyped, WritableTyped } from './stream.model.js';
|
|
6
6
|
import { type TransformLogProgressOptions } from './transform/transformLogProgress.js';
|
|
7
7
|
import { type TransformMapOptions } from './transform/transformMap.js';
|
|
8
8
|
import { type TransformMapSimpleOptions } from './transform/transformMapSimple.js';
|
|
9
9
|
import { type TransformMapSyncOptions } from './transform/transformMapSync.js';
|
|
10
10
|
import { type TransformOffsetOptions } from './transform/transformOffset.js';
|
|
11
|
-
import { type TransformTapOptions } from './transform/transformTap.js';
|
|
12
11
|
import { type TransformThrottleOptions } from './transform/transformThrottle.js';
|
|
13
|
-
export declare class Pipeline<T> {
|
|
12
|
+
export declare class Pipeline<T = unknown> {
|
|
14
13
|
private readonly source;
|
|
15
14
|
private transforms;
|
|
16
15
|
private destination?;
|
|
@@ -19,6 +18,12 @@ export declare class Pipeline<T> {
|
|
|
19
18
|
private abortableSignal;
|
|
20
19
|
private constructor();
|
|
21
20
|
static from<T>(source: ReadableTyped<T>): Pipeline<T>;
|
|
21
|
+
/**
|
|
22
|
+
* Useful in cases when Readable is not immediately available,
|
|
23
|
+
* but only available after an async operation is completed.
|
|
24
|
+
* Implemented via a proxy Transform, which should be transparent.
|
|
25
|
+
*/
|
|
26
|
+
static fromAsyncReadable<T = unknown>(fn: () => Promise<ReadableTyped<T>>): Pipeline<T>;
|
|
22
27
|
static fromWeb<T>(webReadableStream: WebReadableStream<T>): Pipeline<T>;
|
|
23
28
|
/**
|
|
24
29
|
* Technically same as `fromIterable` (since Array is Iterable),
|
|
@@ -58,10 +63,11 @@ export declare class Pipeline<T> {
|
|
|
58
63
|
map<TO>(mapper: AbortableAsyncMapper<T, TO | typeof SKIP | typeof END>, opt?: TransformMapOptions<T, TO>): Pipeline<TO>;
|
|
59
64
|
mapSync<TO>(mapper: IndexedMapper<T, TO | typeof SKIP | typeof END>, opt?: TransformMapSyncOptions): Pipeline<TO>;
|
|
60
65
|
mapSimple<TO>(mapper: IndexedMapper<T, TO>, opt?: TransformMapSimpleOptions): Pipeline<TO>;
|
|
61
|
-
filter(
|
|
66
|
+
filter(asyncPredicate: AsyncPredicate<T>, opt?: TransformMapOptions): this;
|
|
62
67
|
filterSync(predicate: Predicate<T>, opt?: TransformOptions): this;
|
|
63
68
|
offset(opt: TransformOffsetOptions): this;
|
|
64
|
-
tap(fn: AsyncIndexedMapper<T, any>, opt?:
|
|
69
|
+
tap(fn: AsyncIndexedMapper<T, any>, opt?: TransformOptions): this;
|
|
70
|
+
tapSync(fn: IndexedMapper<T, any>, opt?: TransformOptions): this;
|
|
65
71
|
throttle(opt: TransformThrottleOptions): this;
|
|
66
72
|
transform<TO>(transform: TransformTyped<T, TO>): Pipeline<TO>;
|
|
67
73
|
/**
|
|
@@ -90,7 +96,7 @@ export declare class Pipeline<T> {
|
|
|
90
96
|
toFile(outputFilePath: string): Promise<void>;
|
|
91
97
|
toNDJsonFile(outputFilePath: string): Promise<void>;
|
|
92
98
|
to(destination: WritableTyped<T>): Promise<void>;
|
|
93
|
-
forEach(fn: AsyncIndexedMapper<T, void>, opt?: TransformMapOptions<T, void>): Promise<void>;
|
|
94
|
-
forEachSync(fn: IndexedMapper<T, void>, opt?: TransformMapSyncOptions<T, void>): Promise<void>;
|
|
99
|
+
forEach(fn: AsyncIndexedMapper<T, void>, opt?: TransformMapOptions<T, void> & TransformLogProgressOptions<T>): Promise<void>;
|
|
100
|
+
forEachSync(fn: IndexedMapper<T, void>, opt?: TransformMapSyncOptions<T, void> & TransformLogProgressOptions<T>): Promise<void>;
|
|
95
101
|
run(): Promise<void>;
|
|
96
102
|
}
|
package/dist/stream/pipeline.js
CHANGED
|
@@ -3,10 +3,12 @@ import { pipeline } from 'node:stream/promises';
|
|
|
3
3
|
import { createUnzip } from 'node:zlib';
|
|
4
4
|
import { createGzip } from 'node:zlib';
|
|
5
5
|
import { createAbortableSignal } from '@naturalcycles/js-lib';
|
|
6
|
+
import { _passthroughPredicate, } from '@naturalcycles/js-lib/types';
|
|
6
7
|
import { fs2 } from '../fs/fs2.js';
|
|
7
8
|
import { createReadStreamAsNDJson } from './ndjson/createReadStreamAsNDJson.js';
|
|
8
9
|
import { transformJsonParse } from './ndjson/transformJsonParse.js';
|
|
9
10
|
import { transformToNDJson } from './ndjson/transformToNDJson.js';
|
|
11
|
+
import { createReadableFromAsync } from './readable/createReadable.js';
|
|
10
12
|
import { PIPELINE_GRACEFUL_ABORT } from './stream.util.js';
|
|
11
13
|
import { transformChunk } from './transform/transformChunk.js';
|
|
12
14
|
import { transformFilterSync } from './transform/transformFilter.js';
|
|
@@ -19,7 +21,7 @@ import { transformMapSimple, } from './transform/transformMapSimple.js';
|
|
|
19
21
|
import { transformMapSync } from './transform/transformMapSync.js';
|
|
20
22
|
import { transformOffset } from './transform/transformOffset.js';
|
|
21
23
|
import { transformSplitOnNewline } from './transform/transformSplit.js';
|
|
22
|
-
import { transformTap } from './transform/transformTap.js';
|
|
24
|
+
import { transformTap, transformTapSync } from './transform/transformTap.js';
|
|
23
25
|
import { transformThrottle } from './transform/transformThrottle.js';
|
|
24
26
|
import { writablePushToArray } from './writable/writablePushToArray.js';
|
|
25
27
|
import { writableVoid } from './writable/writableVoid.js';
|
|
@@ -39,6 +41,14 @@ export class Pipeline {
|
|
|
39
41
|
static from(source) {
|
|
40
42
|
return new Pipeline(source);
|
|
41
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Useful in cases when Readable is not immediately available,
|
|
46
|
+
* but only available after an async operation is completed.
|
|
47
|
+
* Implemented via a proxy Transform, which should be transparent.
|
|
48
|
+
*/
|
|
49
|
+
static fromAsyncReadable(fn) {
|
|
50
|
+
return new Pipeline(createReadableFromAsync(fn));
|
|
51
|
+
}
|
|
42
52
|
static fromWeb(webReadableStream) {
|
|
43
53
|
return new Pipeline(Readable.fromWeb(webReadableStream));
|
|
44
54
|
}
|
|
@@ -132,9 +142,9 @@ export class Pipeline {
|
|
|
132
142
|
this.transforms.push(transformMapSimple(mapper, opt));
|
|
133
143
|
return this;
|
|
134
144
|
}
|
|
135
|
-
filter(
|
|
145
|
+
filter(asyncPredicate, opt) {
|
|
136
146
|
this.transforms.push(transformMap(v => v, {
|
|
137
|
-
|
|
147
|
+
asyncPredicate,
|
|
138
148
|
...opt,
|
|
139
149
|
signal: this.abortableSignal,
|
|
140
150
|
}));
|
|
@@ -152,6 +162,10 @@ export class Pipeline {
|
|
|
152
162
|
this.transforms.push(transformTap(fn, opt));
|
|
153
163
|
return this;
|
|
154
164
|
}
|
|
165
|
+
tapSync(fn, opt) {
|
|
166
|
+
this.transforms.push(transformTapSync(fn, opt));
|
|
167
|
+
return this;
|
|
168
|
+
}
|
|
155
169
|
throttle(opt) {
|
|
156
170
|
this.transforms.push(transformThrottle(opt));
|
|
157
171
|
return this;
|
|
@@ -257,18 +271,26 @@ export class Pipeline {
|
|
|
257
271
|
this.destination = destination;
|
|
258
272
|
await this.run();
|
|
259
273
|
}
|
|
260
|
-
async forEach(fn, opt) {
|
|
274
|
+
async forEach(fn, opt = {}) {
|
|
261
275
|
this.transforms.push(transformMap(fn, {
|
|
276
|
+
predicate: opt.logEvery ? _passthroughPredicate : undefined, // for the logger to work
|
|
262
277
|
...opt,
|
|
263
278
|
signal: this.abortableSignal,
|
|
264
279
|
}));
|
|
280
|
+
if (opt.logEvery) {
|
|
281
|
+
this.transforms.push(transformLogProgress(opt));
|
|
282
|
+
}
|
|
265
283
|
await this.run();
|
|
266
284
|
}
|
|
267
|
-
async forEachSync(fn, opt) {
|
|
285
|
+
async forEachSync(fn, opt = {}) {
|
|
268
286
|
this.transforms.push(transformMapSync(fn, {
|
|
287
|
+
predicate: opt.logEvery ? _passthroughPredicate : undefined, // for the logger to work
|
|
269
288
|
...opt,
|
|
270
289
|
signal: this.abortableSignal,
|
|
271
290
|
}));
|
|
291
|
+
if (opt.logEvery) {
|
|
292
|
+
this.transforms.push(transformLogProgress(opt));
|
|
293
|
+
}
|
|
272
294
|
await this.run();
|
|
273
295
|
}
|
|
274
296
|
async run() {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ReadableOptions } from 'node:stream';
|
|
2
2
|
import type { ReadableTyped } from '../stream.model.js';
|
|
3
3
|
/**
|
|
4
4
|
* Convenience function to create a Readable that can be pushed into (similar to RxJS Subject).
|
|
@@ -12,8 +12,14 @@ import type { ReadableTyped } from '../stream.model.js';
|
|
|
12
12
|
* if read() will be called AFTER everything was pushed and Readable is closed (by pushing `null`).
|
|
13
13
|
* Beware of it when e.g doing unit testing! Jest prefers to hang (not exit-0).
|
|
14
14
|
*/
|
|
15
|
-
export declare function
|
|
15
|
+
export declare function createReadable<T>(items?: Iterable<T>, opt?: ReadableOptions, onRead?: () => void): ReadableTyped<T>;
|
|
16
16
|
/**
|
|
17
17
|
* Convenience type-safe wrapper around Readable.from() that infers the Type of input.
|
|
18
18
|
*/
|
|
19
|
-
export declare function
|
|
19
|
+
export declare function createReadableFrom<T>(iterable: Iterable<T> | AsyncIterable<T>, opt?: ReadableOptions): ReadableTyped<T>;
|
|
20
|
+
/**
|
|
21
|
+
* Allows to "create Readable asynchronously".
|
|
22
|
+
* Implemented via a proxy Transform, which is created (and returned) eagerly,
|
|
23
|
+
* and later (when source Readable is created) serves as a pass-through proxy.
|
|
24
|
+
*/
|
|
25
|
+
export declare function createReadableFromAsync<T>(fn: () => Promise<ReadableTyped<T>>): ReadableTyped<T>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Transform } from 'node:stream';
|
|
1
2
|
import { Readable } from 'node:stream';
|
|
2
3
|
/**
|
|
3
4
|
* Convenience function to create a Readable that can be pushed into (similar to RxJS Subject).
|
|
@@ -11,7 +12,7 @@ import { Readable } from 'node:stream';
|
|
|
11
12
|
* if read() will be called AFTER everything was pushed and Readable is closed (by pushing `null`).
|
|
12
13
|
* Beware of it when e.g doing unit testing! Jest prefers to hang (not exit-0).
|
|
13
14
|
*/
|
|
14
|
-
export function
|
|
15
|
+
export function createReadable(items = [], opt, onRead) {
|
|
15
16
|
const readable = new Readable({
|
|
16
17
|
objectMode: true,
|
|
17
18
|
...opt,
|
|
@@ -27,6 +28,26 @@ export function readableCreate(items = [], opt, onRead) {
|
|
|
27
28
|
/**
|
|
28
29
|
* Convenience type-safe wrapper around Readable.from() that infers the Type of input.
|
|
29
30
|
*/
|
|
30
|
-
export function
|
|
31
|
+
export function createReadableFrom(iterable, opt) {
|
|
31
32
|
return Readable.from(iterable, opt);
|
|
32
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Allows to "create Readable asynchronously".
|
|
36
|
+
* Implemented via a proxy Transform, which is created (and returned) eagerly,
|
|
37
|
+
* and later (when source Readable is created) serves as a pass-through proxy.
|
|
38
|
+
*/
|
|
39
|
+
export function createReadableFromAsync(fn) {
|
|
40
|
+
const transform = new Transform({
|
|
41
|
+
objectMode: true,
|
|
42
|
+
highWaterMark: 1,
|
|
43
|
+
transform: (chunk, _encoding, cb) => {
|
|
44
|
+
cb(null, chunk);
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
void fn()
|
|
48
|
+
.then(readable => {
|
|
49
|
+
readable.on('error', err => transform.destroy(err)).pipe(transform);
|
|
50
|
+
})
|
|
51
|
+
.catch(err => transform.destroy(err));
|
|
52
|
+
return transform;
|
|
53
|
+
}
|
|
@@ -11,11 +11,11 @@ export interface ReadableArrayOptions {
|
|
|
11
11
|
/** allows destroying the stream if the signal is aborted. */
|
|
12
12
|
signal?: AbortSignal;
|
|
13
13
|
}
|
|
14
|
-
export type ReadableMapper<IN, OUT> = (data: IN, opt?: ReadableSignalOptions) => Promisable<OUT>;
|
|
15
|
-
export type ReadableFlatMapper<IN, OUT> = (data: IN, opt?: ReadableSignalOptions) => Promisable<OUT[]>;
|
|
14
|
+
export type ReadableMapper<IN, OUT = unknown> = (data: IN, opt?: ReadableSignalOptions) => Promisable<OUT>;
|
|
15
|
+
export type ReadableFlatMapper<IN, OUT = unknown> = (data: IN, opt?: ReadableSignalOptions) => Promisable<OUT[]>;
|
|
16
16
|
export type ReadableVoidMapper<IN> = (data: IN, opt?: ReadableSignalOptions) => void | Promise<void>;
|
|
17
17
|
export type ReadablePredicate<IN> = (data: IN, opt?: ReadableSignalOptions) => boolean | Promise<boolean>;
|
|
18
|
-
export interface ReadableTyped<T> extends Readable {
|
|
18
|
+
export interface ReadableTyped<T = unknown> extends Readable {
|
|
19
19
|
toArray: (opt?: ReadableSignalOptions) => Promise<T[]>;
|
|
20
20
|
map: <OUT>(mapper: ReadableMapper<T, OUT>, opt?: ReadableArrayOptions) => ReadableTyped<OUT>;
|
|
21
21
|
flatMap: <OUT>(mapper: ReadableFlatMapper<T, OUT>, opt?: ReadableArrayOptions) => ReadableTyped<OUT>;
|
|
@@ -26,17 +26,7 @@ export interface ReadableTyped<T> extends Readable {
|
|
|
26
26
|
}
|
|
27
27
|
export interface WritableTyped<T> extends Writable {
|
|
28
28
|
}
|
|
29
|
-
|
|
30
|
-
* Type alias that indicates that the Readable is not in objectMode,
|
|
31
|
-
* e.g returns a binary stream (like a gzip stream).
|
|
32
|
-
*/
|
|
33
|
-
export type ReadableBinary = Readable;
|
|
34
|
-
/**
|
|
35
|
-
* Type alias that indicates that the Writable is not in objectMode,
|
|
36
|
-
* e.g reads a binary stream (like a gzip stream).
|
|
37
|
-
*/
|
|
38
|
-
export type WritableBinary = Writable;
|
|
39
|
-
export interface TransformTyped<IN, OUT> extends Transform {
|
|
29
|
+
export interface TransformTyped<IN = unknown, OUT = unknown> extends Transform {
|
|
40
30
|
}
|
|
41
31
|
export interface TransformOptions {
|
|
42
32
|
/**
|
|
@@ -4,7 +4,7 @@ import type { TransformMapOptions } from './transformMap.js';
|
|
|
4
4
|
/**
|
|
5
5
|
* Just a convenience wrapper around `transformMap` that has built-in predicate filtering support.
|
|
6
6
|
*/
|
|
7
|
-
export declare function transformFilter<IN = any>(
|
|
7
|
+
export declare function transformFilter<IN = any>(asyncPredicate: AsyncPredicate<IN>, opt?: TransformMapOptions): TransformTyped<IN, IN>;
|
|
8
8
|
/**
|
|
9
9
|
* Sync version of `transformFilter`
|
|
10
10
|
*/
|
|
@@ -3,9 +3,9 @@ import { transformMap } from './transformMap.js';
|
|
|
3
3
|
/**
|
|
4
4
|
* Just a convenience wrapper around `transformMap` that has built-in predicate filtering support.
|
|
5
5
|
*/
|
|
6
|
-
export function transformFilter(
|
|
6
|
+
export function transformFilter(asyncPredicate, opt = {}) {
|
|
7
7
|
return transformMap(v => v, {
|
|
8
|
-
|
|
8
|
+
asyncPredicate,
|
|
9
9
|
...opt,
|
|
10
10
|
});
|
|
11
11
|
}
|
|
@@ -2,7 +2,7 @@ import { Transform } from 'node:stream';
|
|
|
2
2
|
import { createCommonLoggerAtLevel } from '@naturalcycles/js-lib/log';
|
|
3
3
|
import { pDefer } from '@naturalcycles/js-lib/promise/pDefer.js';
|
|
4
4
|
import { Pipeline } from '../pipeline.js';
|
|
5
|
-
import {
|
|
5
|
+
import { createReadable } from '../readable/createReadable.js';
|
|
6
6
|
/**
|
|
7
7
|
* Allows to "fork" away from the "main pipeline" into the "forked pipeline".
|
|
8
8
|
*
|
|
@@ -14,12 +14,12 @@ export function transformFork(fn, opt = {}) {
|
|
|
14
14
|
const { objectMode = true, highWaterMark } = opt;
|
|
15
15
|
const logger = createCommonLoggerAtLevel(opt.logger, opt.logLevel);
|
|
16
16
|
let lock;
|
|
17
|
-
const fork =
|
|
17
|
+
const fork = createReadable([], {}, () => {
|
|
18
18
|
// `_read` is called
|
|
19
19
|
if (!lock)
|
|
20
20
|
return;
|
|
21
21
|
// We had a lock - let's Resume
|
|
22
|
-
logger.
|
|
22
|
+
logger.debug(`TransformFork: resume`);
|
|
23
23
|
const lockCopy = lock;
|
|
24
24
|
lock = undefined;
|
|
25
25
|
lockCopy.resolve();
|
|
@@ -45,7 +45,7 @@ export function transformFork(fn, opt = {}) {
|
|
|
45
45
|
if (!shouldContinue && !lock) {
|
|
46
46
|
// Forked pipeline indicates that we should Pause
|
|
47
47
|
lock = pDefer();
|
|
48
|
-
logger.
|
|
48
|
+
logger.debug(`TransformFork: pause`);
|
|
49
49
|
}
|
|
50
50
|
// acknowledge that we've finished processing the input chunk
|
|
51
51
|
cb();
|
|
@@ -4,7 +4,7 @@ import { progressLogger } from '../progressLogger.js';
|
|
|
4
4
|
* Pass-through transform that optionally logs progress.
|
|
5
5
|
*/
|
|
6
6
|
export function transformLogProgress(opt = {}) {
|
|
7
|
-
const { objectMode = true, highWaterMark } = opt;
|
|
7
|
+
const { objectMode = true, highWaterMark = 1 } = opt;
|
|
8
8
|
const progress = progressLogger(opt);
|
|
9
9
|
return new Transform({
|
|
10
10
|
objectMode,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type AbortableSignal } from '@naturalcycles/js-lib';
|
|
2
2
|
import { ErrorMode } from '@naturalcycles/js-lib/error';
|
|
3
|
-
import { type AbortableAsyncMapper, type AsyncPredicate, END, type PositiveInteger, type Promisable, SKIP, type StringMap, type UnixTimestampMillis } from '@naturalcycles/js-lib/types';
|
|
3
|
+
import { type AbortableAsyncMapper, type AsyncPredicate, END, type PositiveInteger, type Predicate, type Promisable, SKIP, type StringMap, type UnixTimestampMillis } from '@naturalcycles/js-lib/types';
|
|
4
4
|
import type { TransformOptions, TransformTyped } from '../stream.model.js';
|
|
5
5
|
export interface TransformMapOptions<IN = any, OUT = IN> extends TransformOptions {
|
|
6
6
|
/**
|
|
@@ -10,7 +10,8 @@ export interface TransformMapOptions<IN = any, OUT = IN> extends TransformOption
|
|
|
10
10
|
* Defaults to "pass everything" (including null, undefined, etc).
|
|
11
11
|
* Simpler way to exclude certain cases is to return SKIP symbol from the mapper.
|
|
12
12
|
*/
|
|
13
|
-
predicate?:
|
|
13
|
+
predicate?: Predicate<OUT>;
|
|
14
|
+
asyncPredicate?: AsyncPredicate<OUT>;
|
|
14
15
|
/**
|
|
15
16
|
* Number of concurrently pending promises returned by `mapper`.
|
|
16
17
|
*
|
|
@@ -23,7 +23,7 @@ import { PIPELINE_GRACEFUL_ABORT } from '../stream.util.js';
|
|
|
23
23
|
*/
|
|
24
24
|
export function transformMap(mapper, opt = {}) {
|
|
25
25
|
const { concurrency = 16, highWaterMark = 64, predicate, // we now default to "no predicate" (meaning pass-everything)
|
|
26
|
-
errorMode = ErrorMode.THROW_IMMEDIATELY, onError, onDone, metric = 'stream', signal, } = opt;
|
|
26
|
+
asyncPredicate, errorMode = ErrorMode.THROW_IMMEDIATELY, onError, onDone, metric = 'stream', signal, } = opt;
|
|
27
27
|
const started = Date.now();
|
|
28
28
|
let index = -1;
|
|
29
29
|
let countOut = 0;
|
|
@@ -94,8 +94,20 @@ export function transformMap(mapper, opt = {}) {
|
|
|
94
94
|
// do nothing, don't push
|
|
95
95
|
return cb();
|
|
96
96
|
}
|
|
97
|
-
if (
|
|
98
|
-
|
|
97
|
+
if (predicate) {
|
|
98
|
+
if (predicate(res, currentIndex)) {
|
|
99
|
+
countOut++;
|
|
100
|
+
this.push(res);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else if (asyncPredicate) {
|
|
104
|
+
if ((await asyncPredicate(res, currentIndex)) && !isSettled) {
|
|
105
|
+
// isSettled could have happened in parallel, hence the extra check
|
|
106
|
+
countOut++;
|
|
107
|
+
this.push(res);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
99
111
|
countOut++;
|
|
100
112
|
this.push(res);
|
|
101
113
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { AsyncIndexedMapper } from '@naturalcycles/js-lib/types';
|
|
1
|
+
import type { AsyncIndexedMapper, IndexedMapper } from '@naturalcycles/js-lib/types';
|
|
3
2
|
import type { TransformOptions, TransformTyped } from '../stream.model.js';
|
|
4
|
-
export interface TransformTapOptions extends TransformOptions {
|
|
5
|
-
logger?: CommonLogger;
|
|
6
|
-
}
|
|
7
3
|
/**
|
|
8
4
|
* Similar to RxJS `tap` - allows to run a function for each stream item, without affecting the result.
|
|
9
5
|
* Item is passed through to the output.
|
|
10
6
|
*
|
|
11
7
|
* Can also act as a counter, since `index` is passed to `fn`
|
|
12
8
|
*/
|
|
13
|
-
export declare function transformTap<IN>(fn: AsyncIndexedMapper<IN, any>, opt?:
|
|
9
|
+
export declare function transformTap<IN>(fn: AsyncIndexedMapper<IN, any>, opt?: TransformOptions): TransformTyped<IN, IN>;
|
|
10
|
+
/**
|
|
11
|
+
* Sync version of transformTap
|
|
12
|
+
*/
|
|
13
|
+
export declare function transformTapSync<IN>(fn: IndexedMapper<IN, any>, opt?: TransformOptions): TransformTyped<IN, IN>;
|
|
@@ -6,13 +6,12 @@ import { Transform } from 'node:stream';
|
|
|
6
6
|
* Can also act as a counter, since `index` is passed to `fn`
|
|
7
7
|
*/
|
|
8
8
|
export function transformTap(fn, opt = {}) {
|
|
9
|
-
const { logger = console } = opt;
|
|
9
|
+
const { logger = console, highWaterMark = 1 } = opt;
|
|
10
10
|
let index = -1;
|
|
11
11
|
return new Transform({
|
|
12
12
|
objectMode: true,
|
|
13
|
-
|
|
13
|
+
highWaterMark,
|
|
14
14
|
async transform(chunk, _, cb) {
|
|
15
|
-
// console.log('tap', chunk)
|
|
16
15
|
try {
|
|
17
16
|
await fn(chunk, ++index);
|
|
18
17
|
}
|
|
@@ -20,7 +19,28 @@ export function transformTap(fn, opt = {}) {
|
|
|
20
19
|
logger.error(err);
|
|
21
20
|
// suppressed error
|
|
22
21
|
}
|
|
23
|
-
cb(null, chunk);
|
|
22
|
+
cb(null, chunk);
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Sync version of transformTap
|
|
28
|
+
*/
|
|
29
|
+
export function transformTapSync(fn, opt = {}) {
|
|
30
|
+
const { logger = console, highWaterMark = 1 } = opt;
|
|
31
|
+
let index = -1;
|
|
32
|
+
return new Transform({
|
|
33
|
+
objectMode: true,
|
|
34
|
+
highWaterMark,
|
|
35
|
+
transform(chunk, _, cb) {
|
|
36
|
+
try {
|
|
37
|
+
fn(chunk, ++index);
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
logger.error(err);
|
|
41
|
+
// suppressed error
|
|
42
|
+
}
|
|
43
|
+
cb(null, chunk);
|
|
24
44
|
},
|
|
25
45
|
});
|
|
26
46
|
}
|
|
@@ -5,9 +5,10 @@ import { Writable } from 'node:stream';
|
|
|
5
5
|
* Put it in the end of your pipeline in case it ends with Transform that needs a consumer.
|
|
6
6
|
*/
|
|
7
7
|
export function writableVoid(opt = {}) {
|
|
8
|
+
const { objectMode = true, highWaterMark = 1 } = opt;
|
|
8
9
|
return new Writable({
|
|
9
|
-
objectMode
|
|
10
|
-
|
|
10
|
+
objectMode,
|
|
11
|
+
highWaterMark,
|
|
11
12
|
write(_chunk, _, cb) {
|
|
12
13
|
cb();
|
|
13
14
|
},
|
package/package.json
CHANGED
package/src/stream/index.ts
CHANGED
|
@@ -5,8 +5,8 @@ export * from './ndjson/transformJsonParse.js'
|
|
|
5
5
|
export * from './ndjson/transformToNDJson.js'
|
|
6
6
|
export * from './pipeline.js'
|
|
7
7
|
export * from './progressLogger.js'
|
|
8
|
+
export * from './readable/createReadable.js'
|
|
8
9
|
export * from './readable/readableCombined.js'
|
|
9
|
-
export * from './readable/readableCreate.js'
|
|
10
10
|
export * from './readable/readableFromArray.js'
|
|
11
11
|
export * from './stream.model.js'
|
|
12
12
|
export * from './transform/transformChunk.js'
|
package/src/stream/pipeline.ts
CHANGED
|
@@ -4,22 +4,24 @@ import type { ReadableStream as WebReadableStream } from 'node:stream/web'
|
|
|
4
4
|
import { createUnzip, type ZlibOptions } from 'node:zlib'
|
|
5
5
|
import { createGzip } from 'node:zlib'
|
|
6
6
|
import { createAbortableSignal } from '@naturalcycles/js-lib'
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
7
|
+
import {
|
|
8
|
+
_passthroughPredicate,
|
|
9
|
+
type AbortableAsyncMapper,
|
|
10
|
+
type AsyncIndexedMapper,
|
|
11
|
+
type AsyncPredicate,
|
|
12
|
+
type END,
|
|
13
|
+
type IndexedMapper,
|
|
14
|
+
type Integer,
|
|
15
|
+
type NonNegativeInteger,
|
|
16
|
+
type PositiveInteger,
|
|
17
|
+
type Predicate,
|
|
18
|
+
type SKIP,
|
|
18
19
|
} from '@naturalcycles/js-lib/types'
|
|
19
20
|
import { fs2 } from '../fs/fs2.js'
|
|
20
21
|
import { createReadStreamAsNDJson } from './ndjson/createReadStreamAsNDJson.js'
|
|
21
22
|
import { transformJsonParse } from './ndjson/transformJsonParse.js'
|
|
22
23
|
import { transformToNDJson } from './ndjson/transformToNDJson.js'
|
|
24
|
+
import { createReadableFromAsync } from './readable/createReadable.js'
|
|
23
25
|
import type {
|
|
24
26
|
ReadableTyped,
|
|
25
27
|
TransformOptions,
|
|
@@ -44,12 +46,12 @@ import {
|
|
|
44
46
|
import { transformMapSync, type TransformMapSyncOptions } from './transform/transformMapSync.js'
|
|
45
47
|
import { transformOffset, type TransformOffsetOptions } from './transform/transformOffset.js'
|
|
46
48
|
import { transformSplitOnNewline } from './transform/transformSplit.js'
|
|
47
|
-
import { transformTap,
|
|
49
|
+
import { transformTap, transformTapSync } from './transform/transformTap.js'
|
|
48
50
|
import { transformThrottle, type TransformThrottleOptions } from './transform/transformThrottle.js'
|
|
49
51
|
import { writablePushToArray } from './writable/writablePushToArray.js'
|
|
50
52
|
import { writableVoid } from './writable/writableVoid.js'
|
|
51
53
|
|
|
52
|
-
export class Pipeline<T> {
|
|
54
|
+
export class Pipeline<T = unknown> {
|
|
53
55
|
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: ok
|
|
54
56
|
private readonly source: Readable
|
|
55
57
|
private transforms: NodeJS.ReadWriteStream[] = []
|
|
@@ -68,6 +70,15 @@ export class Pipeline<T> {
|
|
|
68
70
|
return new Pipeline(source)
|
|
69
71
|
}
|
|
70
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Useful in cases when Readable is not immediately available,
|
|
75
|
+
* but only available after an async operation is completed.
|
|
76
|
+
* Implemented via a proxy Transform, which should be transparent.
|
|
77
|
+
*/
|
|
78
|
+
static fromAsyncReadable<T = unknown>(fn: () => Promise<ReadableTyped<T>>): Pipeline<T> {
|
|
79
|
+
return new Pipeline(createReadableFromAsync(fn))
|
|
80
|
+
}
|
|
81
|
+
|
|
71
82
|
static fromWeb<T>(webReadableStream: WebReadableStream<T>): Pipeline<T> {
|
|
72
83
|
return new Pipeline(Readable.fromWeb(webReadableStream))
|
|
73
84
|
}
|
|
@@ -190,10 +201,10 @@ export class Pipeline<T> {
|
|
|
190
201
|
return this as any
|
|
191
202
|
}
|
|
192
203
|
|
|
193
|
-
filter(
|
|
204
|
+
filter(asyncPredicate: AsyncPredicate<T>, opt?: TransformMapOptions): this {
|
|
194
205
|
this.transforms.push(
|
|
195
206
|
transformMap(v => v, {
|
|
196
|
-
|
|
207
|
+
asyncPredicate,
|
|
197
208
|
...opt,
|
|
198
209
|
signal: this.abortableSignal,
|
|
199
210
|
}),
|
|
@@ -211,11 +222,16 @@ export class Pipeline<T> {
|
|
|
211
222
|
return this
|
|
212
223
|
}
|
|
213
224
|
|
|
214
|
-
tap(fn: AsyncIndexedMapper<T, any>, opt?:
|
|
225
|
+
tap(fn: AsyncIndexedMapper<T, any>, opt?: TransformOptions): this {
|
|
215
226
|
this.transforms.push(transformTap(fn, opt))
|
|
216
227
|
return this
|
|
217
228
|
}
|
|
218
229
|
|
|
230
|
+
tapSync(fn: IndexedMapper<T, any>, opt?: TransformOptions): this {
|
|
231
|
+
this.transforms.push(transformTapSync(fn, opt))
|
|
232
|
+
return this
|
|
233
|
+
}
|
|
234
|
+
|
|
219
235
|
throttle(opt: TransformThrottleOptions): this {
|
|
220
236
|
this.transforms.push(transformThrottle(opt))
|
|
221
237
|
return this
|
|
@@ -347,27 +363,35 @@ export class Pipeline<T> {
|
|
|
347
363
|
|
|
348
364
|
async forEach(
|
|
349
365
|
fn: AsyncIndexedMapper<T, void>,
|
|
350
|
-
opt
|
|
366
|
+
opt: TransformMapOptions<T, void> & TransformLogProgressOptions<T> = {},
|
|
351
367
|
): Promise<void> {
|
|
352
368
|
this.transforms.push(
|
|
353
369
|
transformMap(fn, {
|
|
370
|
+
predicate: opt.logEvery ? _passthroughPredicate : undefined, // for the logger to work
|
|
354
371
|
...opt,
|
|
355
372
|
signal: this.abortableSignal,
|
|
356
373
|
}),
|
|
357
374
|
)
|
|
375
|
+
if (opt.logEvery) {
|
|
376
|
+
this.transforms.push(transformLogProgress(opt))
|
|
377
|
+
}
|
|
358
378
|
await this.run()
|
|
359
379
|
}
|
|
360
380
|
|
|
361
381
|
async forEachSync(
|
|
362
382
|
fn: IndexedMapper<T, void>,
|
|
363
|
-
opt
|
|
383
|
+
opt: TransformMapSyncOptions<T, void> & TransformLogProgressOptions<T> = {},
|
|
364
384
|
): Promise<void> {
|
|
365
385
|
this.transforms.push(
|
|
366
386
|
transformMapSync(fn, {
|
|
387
|
+
predicate: opt.logEvery ? _passthroughPredicate : undefined, // for the logger to work
|
|
367
388
|
...opt,
|
|
368
389
|
signal: this.abortableSignal,
|
|
369
390
|
}),
|
|
370
391
|
)
|
|
392
|
+
if (opt.logEvery) {
|
|
393
|
+
this.transforms.push(transformLogProgress(opt))
|
|
394
|
+
}
|
|
371
395
|
await this.run()
|
|
372
396
|
}
|
|
373
397
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ReadableOptions, Transform } from 'node:stream'
|
|
2
2
|
import { Readable } from 'node:stream'
|
|
3
3
|
import type { ReadableTyped } from '../stream.model.js'
|
|
4
4
|
|
|
@@ -14,7 +14,7 @@ import type { ReadableTyped } from '../stream.model.js'
|
|
|
14
14
|
* if read() will be called AFTER everything was pushed and Readable is closed (by pushing `null`).
|
|
15
15
|
* Beware of it when e.g doing unit testing! Jest prefers to hang (not exit-0).
|
|
16
16
|
*/
|
|
17
|
-
export function
|
|
17
|
+
export function createReadable<T>(
|
|
18
18
|
items: Iterable<T> = [],
|
|
19
19
|
opt?: ReadableOptions,
|
|
20
20
|
onRead?: () => void, // read callback
|
|
@@ -35,9 +35,32 @@ export function readableCreate<T>(
|
|
|
35
35
|
/**
|
|
36
36
|
* Convenience type-safe wrapper around Readable.from() that infers the Type of input.
|
|
37
37
|
*/
|
|
38
|
-
export function
|
|
38
|
+
export function createReadableFrom<T>(
|
|
39
39
|
iterable: Iterable<T> | AsyncIterable<T>,
|
|
40
40
|
opt?: ReadableOptions,
|
|
41
41
|
): ReadableTyped<T> {
|
|
42
42
|
return Readable.from(iterable, opt)
|
|
43
43
|
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Allows to "create Readable asynchronously".
|
|
47
|
+
* Implemented via a proxy Transform, which is created (and returned) eagerly,
|
|
48
|
+
* and later (when source Readable is created) serves as a pass-through proxy.
|
|
49
|
+
*/
|
|
50
|
+
export function createReadableFromAsync<T>(fn: () => Promise<ReadableTyped<T>>): ReadableTyped<T> {
|
|
51
|
+
const transform = new Transform({
|
|
52
|
+
objectMode: true,
|
|
53
|
+
highWaterMark: 1,
|
|
54
|
+
transform: (chunk, _encoding, cb) => {
|
|
55
|
+
cb(null, chunk)
|
|
56
|
+
},
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
void fn()
|
|
60
|
+
.then(readable => {
|
|
61
|
+
readable.on('error', err => transform.destroy(err)).pipe(transform)
|
|
62
|
+
})
|
|
63
|
+
.catch(err => transform.destroy(err))
|
|
64
|
+
|
|
65
|
+
return transform
|
|
66
|
+
}
|
|
@@ -14,9 +14,12 @@ export interface ReadableArrayOptions {
|
|
|
14
14
|
signal?: AbortSignal
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export type ReadableMapper<IN, OUT> = (
|
|
17
|
+
export type ReadableMapper<IN, OUT = unknown> = (
|
|
18
|
+
data: IN,
|
|
19
|
+
opt?: ReadableSignalOptions,
|
|
20
|
+
) => Promisable<OUT>
|
|
18
21
|
|
|
19
|
-
export type ReadableFlatMapper<IN, OUT> = (
|
|
22
|
+
export type ReadableFlatMapper<IN, OUT = unknown> = (
|
|
20
23
|
data: IN,
|
|
21
24
|
opt?: ReadableSignalOptions,
|
|
22
25
|
) => Promisable<OUT[]>
|
|
@@ -28,7 +31,7 @@ export type ReadablePredicate<IN> = (
|
|
|
28
31
|
opt?: ReadableSignalOptions,
|
|
29
32
|
) => boolean | Promise<boolean>
|
|
30
33
|
|
|
31
|
-
export interface ReadableTyped<T> extends Readable {
|
|
34
|
+
export interface ReadableTyped<T = unknown> extends Readable {
|
|
32
35
|
toArray: (opt?: ReadableSignalOptions) => Promise<T[]>
|
|
33
36
|
|
|
34
37
|
map: <OUT>(mapper: ReadableMapper<T, OUT>, opt?: ReadableArrayOptions) => ReadableTyped<OUT>
|
|
@@ -49,19 +52,8 @@ export interface ReadableTyped<T> extends Readable {
|
|
|
49
52
|
// biome-ignore lint/correctness/noUnusedVariables: ok
|
|
50
53
|
export interface WritableTyped<T> extends Writable {}
|
|
51
54
|
|
|
52
|
-
/**
|
|
53
|
-
* Type alias that indicates that the Readable is not in objectMode,
|
|
54
|
-
* e.g returns a binary stream (like a gzip stream).
|
|
55
|
-
*/
|
|
56
|
-
export type ReadableBinary = Readable
|
|
57
|
-
/**
|
|
58
|
-
* Type alias that indicates that the Writable is not in objectMode,
|
|
59
|
-
* e.g reads a binary stream (like a gzip stream).
|
|
60
|
-
*/
|
|
61
|
-
export type WritableBinary = Writable
|
|
62
|
-
|
|
63
55
|
// biome-ignore lint/correctness/noUnusedVariables: ok
|
|
64
|
-
export interface TransformTyped<IN, OUT> extends Transform {}
|
|
56
|
+
export interface TransformTyped<IN = unknown, OUT = unknown> extends Transform {}
|
|
65
57
|
|
|
66
58
|
export interface TransformOptions {
|
|
67
59
|
/**
|
|
@@ -8,11 +8,11 @@ import { transformMap } from './transformMap.js'
|
|
|
8
8
|
* Just a convenience wrapper around `transformMap` that has built-in predicate filtering support.
|
|
9
9
|
*/
|
|
10
10
|
export function transformFilter<IN = any>(
|
|
11
|
-
|
|
11
|
+
asyncPredicate: AsyncPredicate<IN>,
|
|
12
12
|
opt: TransformMapOptions = {},
|
|
13
13
|
): TransformTyped<IN, IN> {
|
|
14
14
|
return transformMap(v => v, {
|
|
15
|
-
|
|
15
|
+
asyncPredicate,
|
|
16
16
|
...opt,
|
|
17
17
|
})
|
|
18
18
|
}
|
|
@@ -2,7 +2,7 @@ import { Transform } from 'node:stream'
|
|
|
2
2
|
import { createCommonLoggerAtLevel } from '@naturalcycles/js-lib/log'
|
|
3
3
|
import { type DeferredPromise, pDefer } from '@naturalcycles/js-lib/promise/pDefer.js'
|
|
4
4
|
import { Pipeline } from '../pipeline.js'
|
|
5
|
-
import {
|
|
5
|
+
import { createReadable } from '../readable/createReadable.js'
|
|
6
6
|
import type { TransformOptions, TransformTyped } from '../stream.model.js'
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -21,11 +21,11 @@ export function transformFork<T>(
|
|
|
21
21
|
|
|
22
22
|
let lock: DeferredPromise | undefined
|
|
23
23
|
|
|
24
|
-
const fork =
|
|
24
|
+
const fork = createReadable<T>([], {}, () => {
|
|
25
25
|
// `_read` is called
|
|
26
26
|
if (!lock) return
|
|
27
27
|
// We had a lock - let's Resume
|
|
28
|
-
logger.
|
|
28
|
+
logger.debug(`TransformFork: resume`)
|
|
29
29
|
const lockCopy = lock
|
|
30
30
|
lock = undefined
|
|
31
31
|
lockCopy.resolve()
|
|
@@ -54,7 +54,7 @@ export function transformFork<T>(
|
|
|
54
54
|
if (!shouldContinue && !lock) {
|
|
55
55
|
// Forked pipeline indicates that we should Pause
|
|
56
56
|
lock = pDefer()
|
|
57
|
-
logger.
|
|
57
|
+
logger.debug(`TransformFork: pause`)
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
// acknowledge that we've finished processing the input chunk
|
|
@@ -13,7 +13,7 @@ export interface TransformLogProgressOptions<IN = any>
|
|
|
13
13
|
export function transformLogProgress<IN = any>(
|
|
14
14
|
opt: TransformLogProgressOptions = {},
|
|
15
15
|
): TransformTyped<IN, IN> {
|
|
16
|
-
const { objectMode = true, highWaterMark } = opt
|
|
16
|
+
const { objectMode = true, highWaterMark = 1 } = opt
|
|
17
17
|
const progress = progressLogger(opt)
|
|
18
18
|
|
|
19
19
|
return new Transform({
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type AsyncPredicate,
|
|
9
9
|
END,
|
|
10
10
|
type PositiveInteger,
|
|
11
|
+
type Predicate,
|
|
11
12
|
type Promisable,
|
|
12
13
|
SKIP,
|
|
13
14
|
type StringMap,
|
|
@@ -26,7 +27,9 @@ export interface TransformMapOptions<IN = any, OUT = IN> extends TransformOption
|
|
|
26
27
|
* Defaults to "pass everything" (including null, undefined, etc).
|
|
27
28
|
* Simpler way to exclude certain cases is to return SKIP symbol from the mapper.
|
|
28
29
|
*/
|
|
29
|
-
predicate?:
|
|
30
|
+
predicate?: Predicate<OUT>
|
|
31
|
+
|
|
32
|
+
asyncPredicate?: AsyncPredicate<OUT>
|
|
30
33
|
|
|
31
34
|
/**
|
|
32
35
|
* Number of concurrently pending promises returned by `mapper`.
|
|
@@ -137,6 +140,7 @@ export function transformMap<IN = any, OUT = IN>(
|
|
|
137
140
|
concurrency = 16,
|
|
138
141
|
highWaterMark = 64,
|
|
139
142
|
predicate, // we now default to "no predicate" (meaning pass-everything)
|
|
143
|
+
asyncPredicate,
|
|
140
144
|
errorMode = ErrorMode.THROW_IMMEDIATELY,
|
|
141
145
|
onError,
|
|
142
146
|
onDone,
|
|
@@ -226,8 +230,18 @@ export function transformMap<IN = any, OUT = IN>(
|
|
|
226
230
|
return cb()
|
|
227
231
|
}
|
|
228
232
|
|
|
229
|
-
if (
|
|
230
|
-
|
|
233
|
+
if (predicate) {
|
|
234
|
+
if (predicate(res, currentIndex)) {
|
|
235
|
+
countOut++
|
|
236
|
+
this.push(res)
|
|
237
|
+
}
|
|
238
|
+
} else if (asyncPredicate) {
|
|
239
|
+
if ((await asyncPredicate(res, currentIndex)) && !isSettled) {
|
|
240
|
+
// isSettled could have happened in parallel, hence the extra check
|
|
241
|
+
countOut++
|
|
242
|
+
this.push(res)
|
|
243
|
+
}
|
|
244
|
+
} else {
|
|
231
245
|
countOut++
|
|
232
246
|
this.push(res)
|
|
233
247
|
}
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import { Transform } from 'node:stream'
|
|
2
|
-
import type {
|
|
3
|
-
import type { AsyncIndexedMapper } from '@naturalcycles/js-lib/types'
|
|
2
|
+
import type { AsyncIndexedMapper, IndexedMapper } from '@naturalcycles/js-lib/types'
|
|
4
3
|
import type { TransformOptions, TransformTyped } from '../stream.model.js'
|
|
5
4
|
|
|
6
|
-
export interface TransformTapOptions extends TransformOptions {
|
|
7
|
-
logger?: CommonLogger
|
|
8
|
-
}
|
|
9
|
-
|
|
10
5
|
/**
|
|
11
6
|
* Similar to RxJS `tap` - allows to run a function for each stream item, without affecting the result.
|
|
12
7
|
* Item is passed through to the output.
|
|
@@ -15,17 +10,15 @@ export interface TransformTapOptions extends TransformOptions {
|
|
|
15
10
|
*/
|
|
16
11
|
export function transformTap<IN>(
|
|
17
12
|
fn: AsyncIndexedMapper<IN, any>,
|
|
18
|
-
opt:
|
|
13
|
+
opt: TransformOptions = {},
|
|
19
14
|
): TransformTyped<IN, IN> {
|
|
20
|
-
const { logger = console } = opt
|
|
15
|
+
const { logger = console, highWaterMark = 1 } = opt
|
|
21
16
|
let index = -1
|
|
22
17
|
|
|
23
18
|
return new Transform({
|
|
24
19
|
objectMode: true,
|
|
25
|
-
|
|
20
|
+
highWaterMark,
|
|
26
21
|
async transform(chunk: IN, _, cb) {
|
|
27
|
-
// console.log('tap', chunk)
|
|
28
|
-
|
|
29
22
|
try {
|
|
30
23
|
await fn(chunk, ++index)
|
|
31
24
|
} catch (err) {
|
|
@@ -33,7 +26,33 @@ export function transformTap<IN>(
|
|
|
33
26
|
// suppressed error
|
|
34
27
|
}
|
|
35
28
|
|
|
36
|
-
cb(null, chunk)
|
|
29
|
+
cb(null, chunk)
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Sync version of transformTap
|
|
36
|
+
*/
|
|
37
|
+
export function transformTapSync<IN>(
|
|
38
|
+
fn: IndexedMapper<IN, any>,
|
|
39
|
+
opt: TransformOptions = {},
|
|
40
|
+
): TransformTyped<IN, IN> {
|
|
41
|
+
const { logger = console, highWaterMark = 1 } = opt
|
|
42
|
+
let index = -1
|
|
43
|
+
|
|
44
|
+
return new Transform({
|
|
45
|
+
objectMode: true,
|
|
46
|
+
highWaterMark,
|
|
47
|
+
transform(chunk: IN, _, cb) {
|
|
48
|
+
try {
|
|
49
|
+
fn(chunk, ++index)
|
|
50
|
+
} catch (err) {
|
|
51
|
+
logger.error(err)
|
|
52
|
+
// suppressed error
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
cb(null, chunk)
|
|
37
56
|
},
|
|
38
57
|
})
|
|
39
58
|
}
|
|
@@ -7,9 +7,10 @@ import type { TransformOptions } from '../stream.model.js'
|
|
|
7
7
|
* Put it in the end of your pipeline in case it ends with Transform that needs a consumer.
|
|
8
8
|
*/
|
|
9
9
|
export function writableVoid(opt: TransformOptions = {}): Writable {
|
|
10
|
+
const { objectMode = true, highWaterMark = 1 } = opt
|
|
10
11
|
return new Writable({
|
|
11
|
-
objectMode
|
|
12
|
-
|
|
12
|
+
objectMode,
|
|
13
|
+
highWaterMark,
|
|
13
14
|
write(_chunk, _, cb) {
|
|
14
15
|
cb()
|
|
15
16
|
},
|