@naturalcycles/nodejs-lib 15.22.0 → 15.23.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/exec2/exec2.js +1 -0
- package/dist/stream/index.d.ts +1 -2
- package/dist/stream/index.js +1 -2
- package/dist/stream/ndjson/ndjsonMap.d.ts +1 -1
- package/dist/stream/ndjson/ndjsonMap.js +13 -15
- package/dist/stream/ndjson/ndjsonStreamForEach.d.ts +2 -2
- package/dist/stream/ndjson/ndjsonStreamForEach.js +9 -15
- package/dist/stream/pipeline.d.ts +79 -0
- package/dist/stream/pipeline.js +220 -0
- package/dist/stream/stream.util.d.ts +1 -3
- package/dist/stream/stream.util.js +1 -20
- package/dist/stream/transform/transformChunk.d.ts +5 -8
- package/dist/stream/transform/transformChunk.js +4 -2
- package/dist/stream/transform/transformFlatten.d.ts +1 -0
- package/dist/stream/transform/transformFlatten.js +15 -4
- package/dist/stream/transform/transformLimit.d.ts +3 -26
- package/dist/stream/transform/transformLimit.js +14 -23
- package/dist/stream/transform/transformMap.d.ts +5 -0
- package/dist/stream/transform/transformMap.js +22 -18
- package/dist/stream/transform/transformMapSync.d.ts +5 -3
- package/dist/stream/transform/transformMapSync.js +7 -8
- package/dist/stream/transform/transformTee.js +4 -2
- package/dist/stream/writable/writableForEach.d.ts +2 -1
- package/dist/stream/writable/writableFork.js +2 -2
- package/package.json +1 -1
- package/src/exec2/exec2.ts +1 -0
- package/src/stream/index.ts +1 -2
- package/src/stream/ndjson/ndjsonMap.ts +12 -22
- package/src/stream/ndjson/ndjsonStreamForEach.ts +8 -15
- package/src/stream/pipeline.ts +301 -0
- package/src/stream/stream.util.ts +1 -29
- package/src/stream/transform/transformChunk.ts +8 -11
- package/src/stream/transform/transformFlatten.ts +16 -4
- package/src/stream/transform/transformLimit.ts +20 -51
- package/src/stream/transform/transformMap.ts +31 -20
- package/src/stream/transform/transformMapSync.ts +14 -8
- package/src/stream/transform/transformTee.ts +5 -2
- package/src/stream/writable/writableForEach.ts +2 -2
- package/src/stream/writable/writableFork.ts +2 -2
- package/dist/stream/pipeline/pipeline.d.ts +0 -36
- package/dist/stream/pipeline/pipeline.js +0 -82
- package/dist/stream/readable/readableForEach.d.ts +0 -19
- package/dist/stream/readable/readableForEach.js +0 -30
- package/src/stream/pipeline/pipeline.ts +0 -114
- package/src/stream/readable/readableForEach.ts +0 -42
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { _hc } from '@naturalcycles/js-lib';
|
|
2
2
|
import { _since } from '@naturalcycles/js-lib/datetime/time.util.js';
|
|
3
|
-
import { _anyToError, ErrorMode } from '@naturalcycles/js-lib/error';
|
|
3
|
+
import { _anyToError, _assert, ErrorMode } from '@naturalcycles/js-lib/error';
|
|
4
4
|
import { _stringify } from '@naturalcycles/js-lib/string/stringify.js';
|
|
5
5
|
import { END, SKIP, } from '@naturalcycles/js-lib/types';
|
|
6
6
|
import through2Concurrent from 'through2-concurrent';
|
|
7
7
|
import { yellow } from '../../colors/colors.js';
|
|
8
|
-
import {
|
|
8
|
+
import { PIPELINE_GRACEFUL_ABORT } from '../stream.util.js';
|
|
9
9
|
// doesn't work, cause here we don't construct our Transform instance ourselves
|
|
10
10
|
// export class TransformMap extends AbortableTransform {}
|
|
11
11
|
/**
|
|
@@ -22,11 +22,12 @@ import { pipelineClose } from '../stream.util.js';
|
|
|
22
22
|
*/
|
|
23
23
|
export function transformMap(mapper, opt = {}) {
|
|
24
24
|
const { concurrency = 16, highWaterMark = 64, predicate, // we now default to "no predicate" (meaning pass-everything)
|
|
25
|
-
errorMode = ErrorMode.THROW_IMMEDIATELY, onError, onDone, metric = 'stream', logger = console, } = opt;
|
|
25
|
+
errorMode = ErrorMode.THROW_IMMEDIATELY, onError, onDone, metric = 'stream', logger = console, signal, } = opt;
|
|
26
26
|
const started = Date.now();
|
|
27
27
|
let index = -1;
|
|
28
28
|
let countOut = 0;
|
|
29
29
|
let isSettled = false;
|
|
30
|
+
let ok = true;
|
|
30
31
|
let errors = 0;
|
|
31
32
|
const collectedErrors = []; // only used if errorMode == THROW_AGGREGATED
|
|
32
33
|
return through2Concurrent.obj({
|
|
@@ -57,7 +58,7 @@ export function transformMap(mapper, opt = {}) {
|
|
|
57
58
|
// emit no error
|
|
58
59
|
try {
|
|
59
60
|
await onDone?.({
|
|
60
|
-
ok
|
|
61
|
+
ok,
|
|
61
62
|
collectedErrors,
|
|
62
63
|
countErrors: errors,
|
|
63
64
|
countIn: index + 1,
|
|
@@ -84,7 +85,8 @@ export function transformMap(mapper, opt = {}) {
|
|
|
84
85
|
if (res === END) {
|
|
85
86
|
isSettled = true;
|
|
86
87
|
logger.log(`transformMap END received at index ${currentIndex}`);
|
|
87
|
-
|
|
88
|
+
_assert(signal, 'signal is required when using END');
|
|
89
|
+
signal.abort(new Error(PIPELINE_GRACEFUL_ABORT));
|
|
88
90
|
return cb();
|
|
89
91
|
}
|
|
90
92
|
if (res === SKIP) {
|
|
@@ -110,19 +112,21 @@ export function transformMap(mapper, opt = {}) {
|
|
|
110
112
|
}
|
|
111
113
|
if (errorMode === ErrorMode.THROW_IMMEDIATELY) {
|
|
112
114
|
isSettled = true;
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
115
|
+
ok = false;
|
|
116
|
+
// Tests show that onDone is still called at `final` (second time),
|
|
117
|
+
// so, we no longer call it here
|
|
118
|
+
// try {
|
|
119
|
+
// await onDone?.({
|
|
120
|
+
// ok: false,
|
|
121
|
+
// collectedErrors,
|
|
122
|
+
// countErrors: errors,
|
|
123
|
+
// countIn: index + 1,
|
|
124
|
+
// countOut,
|
|
125
|
+
// started,
|
|
126
|
+
// })
|
|
127
|
+
// } catch (err) {
|
|
128
|
+
// logger.error(err)
|
|
129
|
+
// }
|
|
126
130
|
return cb(err); // Emit error immediately
|
|
127
131
|
}
|
|
128
132
|
if (errorMode === ErrorMode.THROW_AGGREGATED) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import type { AbortableSignal } from '@naturalcycles/js-lib';
|
|
1
2
|
import { ErrorMode } from '@naturalcycles/js-lib/error';
|
|
2
3
|
import type { CommonLogger } from '@naturalcycles/js-lib/log';
|
|
3
4
|
import type { IndexedMapper, Predicate } from '@naturalcycles/js-lib/types';
|
|
4
5
|
import { END, SKIP } from '@naturalcycles/js-lib/types';
|
|
5
|
-
import { AbortableTransform } from '../pipeline/pipeline.js';
|
|
6
6
|
import type { TransformTyped } from '../stream.model.js';
|
|
7
7
|
import type { TransformMapStats } from './transformMap.js';
|
|
8
8
|
export interface TransformMapSyncOptions<IN = any, OUT = IN> {
|
|
@@ -45,8 +45,10 @@ export interface TransformMapSyncOptions<IN = any, OUT = IN> {
|
|
|
45
45
|
*/
|
|
46
46
|
metric?: string;
|
|
47
47
|
logger?: CommonLogger;
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Allows to abort (gracefully stop) the stream from inside the Transform.
|
|
50
|
+
*/
|
|
51
|
+
signal?: AbortableSignal;
|
|
50
52
|
}
|
|
51
53
|
/**
|
|
52
54
|
* Sync (not async) version of transformMap.
|
|
@@ -1,24 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Transform } from 'node:stream';
|
|
2
|
+
import { _anyToError, _assert, ErrorMode } from '@naturalcycles/js-lib/error';
|
|
2
3
|
import { END, SKIP } from '@naturalcycles/js-lib/types';
|
|
3
4
|
import { yellow } from '../../colors/colors.js';
|
|
4
|
-
import {
|
|
5
|
-
import { pipelineClose } from '../stream.util.js';
|
|
6
|
-
export class TransformMapSync extends AbortableTransform {
|
|
7
|
-
}
|
|
5
|
+
import { PIPELINE_GRACEFUL_ABORT } from '../stream.util.js';
|
|
8
6
|
/**
|
|
9
7
|
* Sync (not async) version of transformMap.
|
|
10
8
|
* Supposedly faster, for cases when async is not needed.
|
|
11
9
|
*/
|
|
12
10
|
export function transformMapSync(mapper, opt = {}) {
|
|
13
11
|
const { predicate, // defaults to "no predicate" (pass everything)
|
|
14
|
-
errorMode = ErrorMode.THROW_IMMEDIATELY, onError, onDone, metric = 'stream', objectMode = true, logger = console, } = opt;
|
|
12
|
+
errorMode = ErrorMode.THROW_IMMEDIATELY, onError, onDone, metric = 'stream', objectMode = true, logger = console, signal, } = opt;
|
|
15
13
|
const started = Date.now();
|
|
16
14
|
let index = -1;
|
|
17
15
|
let countOut = 0;
|
|
18
16
|
let isSettled = false;
|
|
19
17
|
let errors = 0;
|
|
20
18
|
const collectedErrors = []; // only used if errorMode == THROW_AGGREGATED
|
|
21
|
-
return new
|
|
19
|
+
return new Transform({
|
|
22
20
|
objectMode,
|
|
23
21
|
...opt,
|
|
24
22
|
transform(chunk, _, cb) {
|
|
@@ -32,7 +30,8 @@ export function transformMapSync(mapper, opt = {}) {
|
|
|
32
30
|
if (v === END) {
|
|
33
31
|
isSettled = true; // will be checked later
|
|
34
32
|
logger.log(`transformMapSync END received at index ${currentIndex}`);
|
|
35
|
-
|
|
33
|
+
_assert(signal, 'signal is required when using END');
|
|
34
|
+
signal.abort(new Error(PIPELINE_GRACEFUL_ABORT));
|
|
36
35
|
return cb();
|
|
37
36
|
}
|
|
38
37
|
if (v === SKIP) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Transform } from 'node:stream';
|
|
2
|
-
import {
|
|
2
|
+
import { pipeline } from 'node:stream/promises';
|
|
3
3
|
import { readableCreate } from '../readable/readableCreate.js';
|
|
4
4
|
/**
|
|
5
5
|
* Allows to "tee"/"fork" away from the "main pipeline" into the "secondary pipeline".
|
|
@@ -12,10 +12,12 @@ import { readableCreate } from '../readable/readableCreate.js';
|
|
|
12
12
|
*/
|
|
13
13
|
export function transformTee(streams) {
|
|
14
14
|
const readable = readableCreate();
|
|
15
|
-
const secondPipelinePromise =
|
|
15
|
+
const secondPipelinePromise = pipeline([readable, ...streams]);
|
|
16
16
|
return new Transform({
|
|
17
17
|
objectMode: true,
|
|
18
18
|
transform(chunk, _, cb) {
|
|
19
|
+
// todo: it's possible to start respecting backpressure,
|
|
20
|
+
// if we start to listen to the boolean output of .push()
|
|
19
21
|
// pass to the "secondary" pipeline
|
|
20
22
|
readable.push(chunk);
|
|
21
23
|
// pass through to the "main" pipeline
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AsyncIndexedMapper, IndexedMapper } from '@naturalcycles/js-lib/types';
|
|
2
2
|
import type { WritableTyped } from '../stream.model.js';
|
|
3
3
|
import { type TransformMapOptions } from '../transform/transformMap.js';
|
|
4
|
+
import { type TransformMapSyncOptions } from '../transform/transformMapSync.js';
|
|
4
5
|
/**
|
|
5
6
|
* Just an alias to transformMap that declares OUT as void.
|
|
6
7
|
*/
|
|
@@ -8,4 +9,4 @@ export declare function writableForEach<IN = any>(mapper: AsyncIndexedMapper<IN,
|
|
|
8
9
|
/**
|
|
9
10
|
* Just an alias to transformMap that declares OUT as void.
|
|
10
11
|
*/
|
|
11
|
-
export declare function writableForEachSync<IN = any>(mapper: IndexedMapper<IN, void>, opt?:
|
|
12
|
+
export declare function writableForEachSync<IN = any>(mapper: IndexedMapper<IN, void>, opt?: TransformMapSyncOptions<IN, void>): WritableTyped<IN>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Writable } from 'node:stream';
|
|
2
|
-
import {
|
|
2
|
+
import { pipeline } from 'node:stream/promises';
|
|
3
3
|
import { readableCreate } from '../readable/readableCreate.js';
|
|
4
4
|
/**
|
|
5
5
|
* Allows "forking" a stream inside pipeline into a number of pipeline chains (2 or more).
|
|
@@ -14,7 +14,7 @@ export function writableFork(chains, opt) {
|
|
|
14
14
|
const allChainsDone = Promise.all(chains.map(async (chain) => {
|
|
15
15
|
const readable = readableCreate();
|
|
16
16
|
readables.push(readable);
|
|
17
|
-
return await
|
|
17
|
+
return await pipeline([readable, ...chain]);
|
|
18
18
|
})).catch(err => {
|
|
19
19
|
console.error(err); // ensure the error is logged
|
|
20
20
|
throw err;
|
package/package.json
CHANGED
package/src/exec2/exec2.ts
CHANGED
package/src/stream/index.ts
CHANGED
|
@@ -5,11 +5,10 @@ export * from './ndjson/ndjsonMap.js'
|
|
|
5
5
|
export * from './ndjson/ndjsonStreamForEach.js'
|
|
6
6
|
export * from './ndjson/transformJsonParse.js'
|
|
7
7
|
export * from './ndjson/transformToNDJson.js'
|
|
8
|
-
export * from './pipeline
|
|
8
|
+
export * from './pipeline.js'
|
|
9
9
|
export * from './progressLogger.js'
|
|
10
10
|
export * from './readable/readableCombined.js'
|
|
11
11
|
export * from './readable/readableCreate.js'
|
|
12
|
-
export * from './readable/readableForEach.js'
|
|
13
12
|
export * from './readable/readableFromArray.js'
|
|
14
13
|
export * from './readable/readableToArray.js'
|
|
15
14
|
export * from './stream.model.js'
|
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
import { ErrorMode } from '@naturalcycles/js-lib/error/errorMode.js'
|
|
2
2
|
import type { AbortableAsyncMapper } from '@naturalcycles/js-lib/types'
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
createWriteStreamAsNDJSON,
|
|
6
|
-
transformFlatten,
|
|
7
|
-
type TransformLogProgressOptions,
|
|
8
|
-
type TransformMapOptions,
|
|
9
|
-
} from '../index.js'
|
|
10
|
-
import { _pipeline, transformLimit, transformLogProgress, transformMap } from '../index.js'
|
|
3
|
+
import type { TransformLogProgressOptions, TransformMapOptions } from '../index.js'
|
|
4
|
+
import { Pipeline } from '../pipeline.js'
|
|
11
5
|
|
|
12
6
|
export interface NDJSONMapOptions<IN = any, OUT = IN>
|
|
13
7
|
extends TransformMapOptions<IN, OUT>,
|
|
@@ -39,20 +33,16 @@ export async function ndjsonMap<IN = any, OUT = any>(
|
|
|
39
33
|
outputFilePath,
|
|
40
34
|
})
|
|
41
35
|
|
|
42
|
-
|
|
43
|
-
limitInput
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
await _pipeline([
|
|
47
|
-
readable,
|
|
48
|
-
transformLogProgress({ metric: 'read', ...opt }),
|
|
49
|
-
transformMap(mapper, {
|
|
36
|
+
await Pipeline.fromNDJsonFile<IN>(inputFilePath)
|
|
37
|
+
.limitSource(limitInput)
|
|
38
|
+
.logProgress({ metric: 'read', ...opt })
|
|
39
|
+
.map(mapper, {
|
|
50
40
|
errorMode: ErrorMode.SUPPRESS,
|
|
51
41
|
...opt,
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
42
|
+
})
|
|
43
|
+
.flattenIfNeeded()
|
|
44
|
+
// .typeCastAs<OUT>()
|
|
45
|
+
.limit(limitOutput)
|
|
46
|
+
.logProgress({ metric: 'saved', logEvery: logEveryOutput })
|
|
47
|
+
.toNDJsonFile(outputFilePath)
|
|
58
48
|
}
|
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
import { ErrorMode } from '@naturalcycles/js-lib/error/errorMode.js'
|
|
2
2
|
import type { AbortableAsyncMapper } from '@naturalcycles/js-lib/types'
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
type TransformLogProgressOptions,
|
|
7
|
-
} from '../transform/transformLogProgress.js'
|
|
8
|
-
import { transformMap, type TransformMapOptions } from '../transform/transformMap.js'
|
|
9
|
-
import { writableVoid } from '../writable/writableVoid.js'
|
|
10
|
-
import { createReadStreamAsNDJSON } from './createReadStreamAsNDJSON.js'
|
|
3
|
+
import { Pipeline } from '../pipeline.js'
|
|
4
|
+
import type { TransformLogProgressOptions } from '../transform/transformLogProgress.js'
|
|
5
|
+
import type { TransformMapOptions } from '../transform/transformMap.js'
|
|
11
6
|
|
|
12
7
|
export interface NDJSONStreamForEachOptions<IN = any>
|
|
13
8
|
extends TransformMapOptions<IN, void>,
|
|
@@ -22,14 +17,12 @@ export async function ndjsonStreamForEach<T>(
|
|
|
22
17
|
mapper: AbortableAsyncMapper<T, void>,
|
|
23
18
|
opt: NDJSONStreamForEachOptions<T>,
|
|
24
19
|
): Promise<void> {
|
|
25
|
-
await
|
|
26
|
-
|
|
27
|
-
transformMap<T, any>(mapper, {
|
|
20
|
+
await Pipeline.fromNDJsonFile<T>(opt.inputFilePath)
|
|
21
|
+
.map(mapper, {
|
|
28
22
|
errorMode: ErrorMode.THROW_AGGREGATED,
|
|
29
23
|
...opt,
|
|
30
24
|
predicate: () => true, // to log progress properly
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
])
|
|
25
|
+
})
|
|
26
|
+
.logProgress(opt)
|
|
27
|
+
.run()
|
|
35
28
|
}
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { Readable, type Transform } from 'node:stream'
|
|
2
|
+
import { pipeline } from 'node:stream/promises'
|
|
3
|
+
import { createGzip } from 'node:zlib'
|
|
4
|
+
import { createAbortableSignal } from '@naturalcycles/js-lib'
|
|
5
|
+
import type {
|
|
6
|
+
AbortableAsyncMapper,
|
|
7
|
+
AsyncIndexedMapper,
|
|
8
|
+
AsyncPredicate,
|
|
9
|
+
END,
|
|
10
|
+
IndexedMapper,
|
|
11
|
+
Integer,
|
|
12
|
+
NonNegativeInteger,
|
|
13
|
+
PositiveInteger,
|
|
14
|
+
Predicate,
|
|
15
|
+
SKIP,
|
|
16
|
+
} from '@naturalcycles/js-lib/types'
|
|
17
|
+
import { fs2 } from '../fs/fs2.js'
|
|
18
|
+
import { createReadStreamAsNDJSON } from './ndjson/createReadStreamAsNDJSON.js'
|
|
19
|
+
import { transformToNDJson } from './ndjson/transformToNDJson.js'
|
|
20
|
+
import type {
|
|
21
|
+
ReadableTyped,
|
|
22
|
+
TransformOptions,
|
|
23
|
+
TransformTyped,
|
|
24
|
+
WritableTyped,
|
|
25
|
+
} from './stream.model.js'
|
|
26
|
+
import { PIPELINE_GRACEFUL_ABORT } from './stream.util.js'
|
|
27
|
+
import { transformChunk } from './transform/transformChunk.js'
|
|
28
|
+
import { transformFilterSync } from './transform/transformFilter.js'
|
|
29
|
+
import { transformFlatten, transformFlattenIfNeeded } from './transform/transformFlatten.js'
|
|
30
|
+
import { transformLimit } from './transform/transformLimit.js'
|
|
31
|
+
import {
|
|
32
|
+
transformLogProgress,
|
|
33
|
+
type TransformLogProgressOptions,
|
|
34
|
+
} from './transform/transformLogProgress.js'
|
|
35
|
+
import { transformMap, type TransformMapOptions } from './transform/transformMap.js'
|
|
36
|
+
import {
|
|
37
|
+
transformMapSimple,
|
|
38
|
+
type TransformMapSimpleOptions,
|
|
39
|
+
} from './transform/transformMapSimple.js'
|
|
40
|
+
import { transformMapSync, type TransformMapSyncOptions } from './transform/transformMapSync.js'
|
|
41
|
+
import { transformOffset, type TransformOffsetOptions } from './transform/transformOffset.js'
|
|
42
|
+
import { transformTap, type TransformTapOptions } from './transform/transformTap.js'
|
|
43
|
+
import { transformThrottle, type TransformThrottleOptions } from './transform/transformThrottle.js'
|
|
44
|
+
import { writablePushToArray } from './writable/writablePushToArray.js'
|
|
45
|
+
import { writableVoid } from './writable/writableVoid.js'
|
|
46
|
+
|
|
47
|
+
export class Pipeline<T> {
|
|
48
|
+
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: ok
|
|
49
|
+
private readonly source: Readable
|
|
50
|
+
private transforms: NodeJS.ReadWriteStream[] = []
|
|
51
|
+
private destination?: NodeJS.WritableStream
|
|
52
|
+
private readableLimit?: Integer
|
|
53
|
+
private abortableSignal = createAbortableSignal()
|
|
54
|
+
|
|
55
|
+
private constructor(source: ReadableTyped<T>) {
|
|
56
|
+
this.source = source
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static from<T>(source: ReadableTyped<T>): Pipeline<T> {
|
|
60
|
+
return new Pipeline(source)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Technically same as `fromIterable` (since Array is Iterable),
|
|
65
|
+
* but named a bit friendlier.
|
|
66
|
+
*/
|
|
67
|
+
static fromArray<T>(input: T[]): Pipeline<T> {
|
|
68
|
+
return new Pipeline(Readable.from(input))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
static fromIterable<T>(input: Iterable<T> | AsyncIterable<T>): Pipeline<T> {
|
|
72
|
+
return new Pipeline(Readable.from(input))
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
static fromNDJsonFile<T>(sourceFilePath: string): Pipeline<T> {
|
|
76
|
+
return new Pipeline(createReadStreamAsNDJSON<T>(sourceFilePath))
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Limits the source Readable, but using `.take(limit)` on it.
|
|
81
|
+
* This is THE preferred way of limiting the source.
|
|
82
|
+
*/
|
|
83
|
+
limitSource(limit: NonNegativeInteger | undefined): this {
|
|
84
|
+
this.readableLimit = limit
|
|
85
|
+
return this
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* If possible - STRONGLY PREFER applying `.take(limit)` on the source Readable,
|
|
90
|
+
* as it's a clean graceful way of limiting the Readable. Example:
|
|
91
|
+
*
|
|
92
|
+
* Pipeline.from(myReadable.take(10))
|
|
93
|
+
*
|
|
94
|
+
* or
|
|
95
|
+
*
|
|
96
|
+
* Pipeline
|
|
97
|
+
* .from(myReadable)
|
|
98
|
+
* .limitSource(10)
|
|
99
|
+
*
|
|
100
|
+
* If applying `take` on Readable is not possible - use this method at your own risk.
|
|
101
|
+
* Why warning?
|
|
102
|
+
* The limit works by aborting the stream, and then catching the error - certainly
|
|
103
|
+
* less clean than `.take()` on the source.
|
|
104
|
+
*/
|
|
105
|
+
limit(limit: NonNegativeInteger | undefined): this {
|
|
106
|
+
this.transforms.push(
|
|
107
|
+
transformLimit({
|
|
108
|
+
limit,
|
|
109
|
+
signal: this.abortableSignal,
|
|
110
|
+
}),
|
|
111
|
+
)
|
|
112
|
+
return this
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
chunk(chunkSize: PositiveInteger, opt?: TransformOptions): Pipeline<T[]> {
|
|
116
|
+
this.transforms.push(transformChunk(chunkSize, opt))
|
|
117
|
+
return this as any
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
flatten<TO>(this: Pipeline<readonly TO[]>): Pipeline<TO> {
|
|
121
|
+
this.transforms.push(transformFlatten())
|
|
122
|
+
return this as any
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
flattenIfNeeded(): Pipeline<T extends readonly (infer TO)[] ? TO : T> {
|
|
126
|
+
this.transforms.push(transformFlattenIfNeeded())
|
|
127
|
+
return this as any
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// TransformLogProgressOptions intentionally doesn't have <T> passed, as it's inconvenient in many cases
|
|
131
|
+
logProgress(opt?: TransformLogProgressOptions): this {
|
|
132
|
+
this.transforms.push(transformLogProgress(opt))
|
|
133
|
+
return this
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
map<TO>(
|
|
137
|
+
mapper: AbortableAsyncMapper<T, TO | typeof SKIP | typeof END>,
|
|
138
|
+
opt?: TransformMapOptions<T, TO>,
|
|
139
|
+
): Pipeline<TO> {
|
|
140
|
+
this.transforms.push(
|
|
141
|
+
transformMap(mapper, {
|
|
142
|
+
...opt,
|
|
143
|
+
signal: this.abortableSignal,
|
|
144
|
+
}),
|
|
145
|
+
)
|
|
146
|
+
return this as any
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
mapSync<TO>(
|
|
150
|
+
mapper: IndexedMapper<T, TO | typeof SKIP | typeof END>,
|
|
151
|
+
opt?: TransformMapSyncOptions,
|
|
152
|
+
): Pipeline<TO> {
|
|
153
|
+
this.transforms.push(
|
|
154
|
+
transformMapSync(mapper, {
|
|
155
|
+
...opt,
|
|
156
|
+
signal: this.abortableSignal,
|
|
157
|
+
}),
|
|
158
|
+
)
|
|
159
|
+
return this as any
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
mapSimple<TO>(mapper: IndexedMapper<T, TO>, opt?: TransformMapSimpleOptions): Pipeline<TO> {
|
|
163
|
+
this.transforms.push(transformMapSimple(mapper, opt))
|
|
164
|
+
return this as any
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
filter(predicate: AsyncPredicate<T>, opt?: TransformMapOptions): this {
|
|
168
|
+
this.transforms.push(
|
|
169
|
+
transformMap(v => v, {
|
|
170
|
+
predicate,
|
|
171
|
+
...opt,
|
|
172
|
+
signal: this.abortableSignal,
|
|
173
|
+
}),
|
|
174
|
+
)
|
|
175
|
+
return this
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
filterSync(predicate: Predicate<T>, opt?: TransformOptions): this {
|
|
179
|
+
this.transforms.push(transformFilterSync(predicate, opt))
|
|
180
|
+
return this
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
offset(opt: TransformOffsetOptions): this {
|
|
184
|
+
this.transforms.push(transformOffset(opt))
|
|
185
|
+
return this
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
tap(fn: AsyncIndexedMapper<T, any>, opt?: TransformTapOptions): this {
|
|
189
|
+
this.transforms.push(transformTap(fn, opt))
|
|
190
|
+
return this
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
throttle(opt: TransformThrottleOptions): this {
|
|
194
|
+
this.transforms.push(transformThrottle(opt))
|
|
195
|
+
return this
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// todo: tee/fork
|
|
199
|
+
|
|
200
|
+
transform<TO>(transform: TransformTyped<T, TO>): Pipeline<TO> {
|
|
201
|
+
this.transforms.push(transform)
|
|
202
|
+
return this as any
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Helper method to add multiple transforms at once.
|
|
207
|
+
* Not type safe! Prefer using singular `transform()` multiple times for type safety.
|
|
208
|
+
*/
|
|
209
|
+
transformMany<TO>(transforms: Transform[]): Pipeline<TO> {
|
|
210
|
+
this.transforms.push(...transforms)
|
|
211
|
+
return this as any
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Utility method just to conveniently type-cast the current Pipeline type.
|
|
216
|
+
* No runtime effect.
|
|
217
|
+
*/
|
|
218
|
+
typeCastAs<TO>(): Pipeline<TO> {
|
|
219
|
+
return this as any
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async toArray(opt?: TransformOptions): Promise<T[]> {
|
|
223
|
+
const arr: T[] = []
|
|
224
|
+
this.destination = writablePushToArray(arr, opt)
|
|
225
|
+
await this.run()
|
|
226
|
+
return arr
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async toFile(outputFilePath: string): Promise<void> {
|
|
230
|
+
fs2.ensureFile(outputFilePath)
|
|
231
|
+
this.destination = fs2.createWriteStream(outputFilePath)
|
|
232
|
+
await this.run()
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async toNDJsonFile(outputFilePath: string): Promise<void> {
|
|
236
|
+
fs2.ensureFile(outputFilePath)
|
|
237
|
+
this.transforms.push(transformToNDJson())
|
|
238
|
+
if (outputFilePath.endsWith('.gz')) {
|
|
239
|
+
this.transforms.push(
|
|
240
|
+
createGzip({
|
|
241
|
+
// chunkSize: 64 * 1024, // no observed speedup
|
|
242
|
+
}),
|
|
243
|
+
)
|
|
244
|
+
}
|
|
245
|
+
this.destination = fs2.createWriteStream(outputFilePath, {
|
|
246
|
+
// highWaterMark: 64 * 1024, // no observed speedup
|
|
247
|
+
})
|
|
248
|
+
await this.run()
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async to(destination: WritableTyped<T>): Promise<void> {
|
|
252
|
+
this.destination = destination
|
|
253
|
+
await this.run()
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async forEach(
|
|
257
|
+
fn: AsyncIndexedMapper<T, void>,
|
|
258
|
+
opt?: TransformMapOptions<T, void>,
|
|
259
|
+
): Promise<void> {
|
|
260
|
+
this.transforms.push(
|
|
261
|
+
transformMap(fn, {
|
|
262
|
+
...opt,
|
|
263
|
+
signal: this.abortableSignal,
|
|
264
|
+
}),
|
|
265
|
+
)
|
|
266
|
+
await this.run()
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async forEachSync(
|
|
270
|
+
fn: IndexedMapper<T, void>,
|
|
271
|
+
opt?: TransformMapSyncOptions<T, void>,
|
|
272
|
+
): Promise<void> {
|
|
273
|
+
this.transforms.push(
|
|
274
|
+
transformMapSync(fn, {
|
|
275
|
+
...opt,
|
|
276
|
+
signal: this.abortableSignal,
|
|
277
|
+
}),
|
|
278
|
+
)
|
|
279
|
+
await this.run()
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async run(): Promise<void> {
|
|
283
|
+
this.destination ||= writableVoid()
|
|
284
|
+
let { source } = this
|
|
285
|
+
if (this.readableLimit) {
|
|
286
|
+
source = source.take(this.readableLimit)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
await pipeline([source, ...this.transforms, this.destination], {
|
|
291
|
+
signal: this.abortableSignal,
|
|
292
|
+
})
|
|
293
|
+
} catch (err) {
|
|
294
|
+
if (err instanceof Error && (err.cause as any)?.message === PIPELINE_GRACEFUL_ABORT) {
|
|
295
|
+
console.log('pipeline gracefully aborted') // todo: this message may be removed later
|
|
296
|
+
return
|
|
297
|
+
}
|
|
298
|
+
throw err
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
@@ -1,29 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import type { CommonLogger } from '@naturalcycles/js-lib/log'
|
|
3
|
-
|
|
4
|
-
export function pipelineClose(
|
|
5
|
-
name: string,
|
|
6
|
-
readableDownstream: Readable,
|
|
7
|
-
sourceReadable: Readable | undefined,
|
|
8
|
-
streamDone: Promise<void> | undefined,
|
|
9
|
-
logger: CommonLogger,
|
|
10
|
-
): void {
|
|
11
|
-
readableDownstream.push(null) // this closes the stream, so downstream Readable will receive `end` and won't write anything
|
|
12
|
-
|
|
13
|
-
if (!sourceReadable) {
|
|
14
|
-
logger.warn(`${name} sourceReadable is not provided, readable stream will not be stopped`)
|
|
15
|
-
} else {
|
|
16
|
-
logger.log(`${name} is calling readable.unpipe() to pause the stream`)
|
|
17
|
-
sourceReadable.unpipe() // it is expected to pause the stream
|
|
18
|
-
|
|
19
|
-
if (!streamDone) {
|
|
20
|
-
logger.log(`${name} streamDone is not provided, will do readable.destroy right away`)
|
|
21
|
-
sourceReadable.destroy()
|
|
22
|
-
} else {
|
|
23
|
-
void streamDone.then(() => {
|
|
24
|
-
logger.log(`${name} streamDone, calling readable.destroy()`)
|
|
25
|
-
sourceReadable.destroy() // this throws ERR_STREAM_PREMATURE_CLOSE
|
|
26
|
-
})
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
1
|
+
export const PIPELINE_GRACEFUL_ABORT = 'PIPELINE_GRACEFUL_ABORT'
|