@naturalcycles/nodejs-lib 15.70.1 → 15.72.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 +0 -1
- package/dist/stream/index.js +0 -1
- package/dist/stream/pipeline.d.ts +13 -8
- package/dist/stream/pipeline.js +18 -32
- package/dist/stream/transform/transformFilter.js +2 -2
- package/dist/stream/transform/transformMap.d.ts +13 -25
- package/dist/stream/transform/transformMap.js +134 -118
- package/dist/stream/transform/worker/transformMultiThreaded.js +59 -39
- package/dist/zip/zip.util.d.ts +4 -1
- package/dist/zip/zip.util.js +14 -2
- package/package.json +1 -3
- package/src/stream/index.ts +0 -1
- package/src/stream/pipeline.ts +21 -48
- package/src/stream/transform/transformFilter.ts +2 -2
- package/src/stream/transform/transformMap.ts +168 -153
- package/src/stream/transform/worker/transformMultiThreaded.ts +57 -40
- package/src/stream/transform/worker/workerClassProxy.js +0 -4
- package/src/zip/zip.util.ts +15 -1
- package/dist/stream/transform/transformMap2.d.ts +0 -66
- package/dist/stream/transform/transformMap2.js +0 -171
- package/src/stream/transform/transformMap2.ts +0 -283
package/dist/stream/index.d.ts
CHANGED
|
@@ -16,7 +16,6 @@ export * from './transform/transformFork.js';
|
|
|
16
16
|
export * from './transform/transformLimit.js';
|
|
17
17
|
export * from './transform/transformLogProgress.js';
|
|
18
18
|
export * from './transform/transformMap.js';
|
|
19
|
-
export * from './transform/transformMap2.js';
|
|
20
19
|
export * from './transform/transformMapSimple.js';
|
|
21
20
|
export * from './transform/transformMapSync.js';
|
|
22
21
|
export * from './transform/transformNoOp.js';
|
package/dist/stream/index.js
CHANGED
|
@@ -16,7 +16,6 @@ export * from './transform/transformFork.js';
|
|
|
16
16
|
export * from './transform/transformLimit.js';
|
|
17
17
|
export * from './transform/transformLogProgress.js';
|
|
18
18
|
export * from './transform/transformMap.js';
|
|
19
|
-
export * from './transform/transformMap2.js';
|
|
20
19
|
export * from './transform/transformMapSimple.js';
|
|
21
20
|
export * from './transform/transformMapSync.js';
|
|
22
21
|
export * from './transform/transformNoOp.js';
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { type Transform } from 'node:stream';
|
|
2
2
|
import type { ReadableStream as WebReadableStream } from 'node:stream/web';
|
|
3
3
|
import { type ZlibOptions, type ZstdOptions } from 'node:zlib';
|
|
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';
|
|
4
|
+
import { type AbortableAsyncMapper, type AsyncIndexedMapper, type AsyncPredicate, type END, type IndexedMapper, type Integer, 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
|
-
import { type TransformMap2Options } from './transform/transformMap2.js';
|
|
9
8
|
import { type TransformMapSimpleOptions } from './transform/transformMapSimple.js';
|
|
10
9
|
import { type TransformMapSyncOptions } from './transform/transformMapSync.js';
|
|
11
10
|
import { type TransformOffsetOptions } from './transform/transformOffset.js';
|
|
@@ -62,8 +61,7 @@ export declare class Pipeline<T = unknown> {
|
|
|
62
61
|
flatten<TO>(this: Pipeline<readonly TO[]>): Pipeline<TO>;
|
|
63
62
|
flattenIfNeeded(): Pipeline<T extends readonly (infer TO)[] ? TO : T>;
|
|
64
63
|
logProgress(opt?: TransformLogProgressOptions): this;
|
|
65
|
-
|
|
66
|
-
map<TO>(mapper: AbortableAsyncMapper<T, TO | typeof SKIP | typeof END>, opt?: TransformMap2Options<T, TO>): Pipeline<TO>;
|
|
64
|
+
map<TO>(mapper: AbortableAsyncMapper<T, TO | typeof SKIP | typeof END>, opt?: TransformMapOptions<T, TO>): Pipeline<TO>;
|
|
67
65
|
mapSync<TO>(mapper: IndexedMapper<T, TO | typeof SKIP | typeof END>, opt?: TransformMapSyncOptions): Pipeline<TO>;
|
|
68
66
|
mapSimple<TO>(mapper: IndexedMapper<T, TO>, opt?: TransformMapSimpleOptions): Pipeline<TO>;
|
|
69
67
|
filter(asyncPredicate: AsyncPredicate<T>, opt?: TransformMapOptions): this;
|
|
@@ -99,14 +97,21 @@ export declare class Pipeline<T = unknown> {
|
|
|
99
97
|
parseJson<TO = unknown>(this: Pipeline<Buffer> | Pipeline<Uint8Array> | Pipeline<string>): Pipeline<TO>;
|
|
100
98
|
gzip(this: Pipeline<Uint8Array>, opt?: ZlibOptions): Pipeline<Uint8Array>;
|
|
101
99
|
gunzip(this: Pipeline<Uint8Array>, opt?: ZlibOptions): Pipeline<Uint8Array>;
|
|
102
|
-
zstdCompress(this: Pipeline<Uint8Array>,
|
|
100
|
+
zstdCompress(this: Pipeline<Uint8Array>, level?: Integer, // defaults to 3
|
|
101
|
+
opt?: ZstdOptions): Pipeline<Uint8Array>;
|
|
103
102
|
zstdDecompress(this: Pipeline<Uint8Array>, opt?: ZstdOptions): Pipeline<Uint8Array>;
|
|
104
103
|
toArray(opt?: TransformOptions): Promise<T[]>;
|
|
105
104
|
toFile(outputFilePath: string): Promise<void>;
|
|
106
|
-
|
|
105
|
+
/**
|
|
106
|
+
* level corresponds to zstd compression level (if filename ends with .zst),
|
|
107
|
+
* or gzip compression level (if filename ends with .gz).
|
|
108
|
+
* Default levels are:
|
|
109
|
+
* gzip: 6
|
|
110
|
+
* zlib: 3 (optimized for throughput, not size, may be larger than gzip at its default level)
|
|
111
|
+
*/
|
|
112
|
+
toNDJsonFile(outputFilePath: string, level?: Integer): Promise<void>;
|
|
107
113
|
to(destination: WritableTyped<T>): Promise<void>;
|
|
108
|
-
|
|
109
|
-
forEach(fn: AsyncIndexedMapper<T, void>, opt?: TransformMap2Options<T, void> & TransformLogProgressOptions<T>): Promise<void>;
|
|
114
|
+
forEach(fn: AsyncIndexedMapper<T, void>, opt?: TransformMapOptions<T, void> & TransformLogProgressOptions<T>): Promise<void>;
|
|
110
115
|
forEachSync(fn: IndexedMapper<T, void>, opt?: TransformMapSyncOptions<T, void> & TransformLogProgressOptions<T>): Promise<void>;
|
|
111
116
|
run(): Promise<void>;
|
|
112
117
|
}
|
package/dist/stream/pipeline.js
CHANGED
|
@@ -4,6 +4,7 @@ import { createGzip, createUnzip, createZstdCompress, createZstdDecompress, } fr
|
|
|
4
4
|
import { createAbortableSignal } from '@naturalcycles/js-lib';
|
|
5
5
|
import { _passthroughPredicate, } from '@naturalcycles/js-lib/types';
|
|
6
6
|
import { fs2 } from '../fs/fs2.js';
|
|
7
|
+
import { zstdLevelToOptions } from '../zip/zip.util.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';
|
|
@@ -16,7 +17,6 @@ import { transformFork } from './transform/transformFork.js';
|
|
|
16
17
|
import { transformLimit } from './transform/transformLimit.js';
|
|
17
18
|
import { transformLogProgress, } from './transform/transformLogProgress.js';
|
|
18
19
|
import { transformMap } from './transform/transformMap.js';
|
|
19
|
-
import { transformMap2 } from './transform/transformMap2.js';
|
|
20
20
|
import { transformMapSimple, } from './transform/transformMapSimple.js';
|
|
21
21
|
import { transformMapSync } from './transform/transformMapSync.js';
|
|
22
22
|
import { transformOffset } from './transform/transformOffset.js';
|
|
@@ -128,15 +128,8 @@ export class Pipeline {
|
|
|
128
128
|
this.transforms.push(transformLogProgress(opt));
|
|
129
129
|
return this;
|
|
130
130
|
}
|
|
131
|
-
mapLegacy(mapper, opt) {
|
|
132
|
-
this.transforms.push(transformMap(mapper, {
|
|
133
|
-
...opt,
|
|
134
|
-
signal: this.abortableSignal,
|
|
135
|
-
}));
|
|
136
|
-
return this;
|
|
137
|
-
}
|
|
138
131
|
map(mapper, opt) {
|
|
139
|
-
this.transforms.push(
|
|
132
|
+
this.transforms.push(transformMap(mapper, {
|
|
140
133
|
...opt,
|
|
141
134
|
signal: this.abortableSignal,
|
|
142
135
|
}));
|
|
@@ -154,7 +147,7 @@ export class Pipeline {
|
|
|
154
147
|
return this;
|
|
155
148
|
}
|
|
156
149
|
filter(asyncPredicate, opt) {
|
|
157
|
-
this.transforms.push(
|
|
150
|
+
this.transforms.push(transformMap(v => v, {
|
|
158
151
|
asyncPredicate,
|
|
159
152
|
...opt,
|
|
160
153
|
signal: this.abortableSignal,
|
|
@@ -261,11 +254,9 @@ export class Pipeline {
|
|
|
261
254
|
this.objectMode = false;
|
|
262
255
|
return this;
|
|
263
256
|
}
|
|
264
|
-
zstdCompress(
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
...opt,
|
|
268
|
-
}));
|
|
257
|
+
zstdCompress(level, // defaults to 3
|
|
258
|
+
opt) {
|
|
259
|
+
this.transforms.push(createZstdCompress(zstdLevelToOptions(level, opt)));
|
|
269
260
|
this.objectMode = false;
|
|
270
261
|
return this;
|
|
271
262
|
}
|
|
@@ -288,18 +279,24 @@ export class Pipeline {
|
|
|
288
279
|
this.destination = fs2.createWriteStream(outputFilePath);
|
|
289
280
|
await this.run();
|
|
290
281
|
}
|
|
291
|
-
|
|
282
|
+
/**
|
|
283
|
+
* level corresponds to zstd compression level (if filename ends with .zst),
|
|
284
|
+
* or gzip compression level (if filename ends with .gz).
|
|
285
|
+
* Default levels are:
|
|
286
|
+
* gzip: 6
|
|
287
|
+
* zlib: 3 (optimized for throughput, not size, may be larger than gzip at its default level)
|
|
288
|
+
*/
|
|
289
|
+
async toNDJsonFile(outputFilePath, level) {
|
|
292
290
|
fs2.ensureFile(outputFilePath);
|
|
293
291
|
this.transforms.push(transformToNDJson());
|
|
294
292
|
if (outputFilePath.endsWith('.gz')) {
|
|
295
293
|
this.transforms.push(createGzip({
|
|
296
|
-
|
|
294
|
+
level,
|
|
295
|
+
// chunkSize: 64 * 1024, // no observed speedup
|
|
297
296
|
}));
|
|
298
297
|
}
|
|
299
298
|
else if (outputFilePath.endsWith('.zst')) {
|
|
300
|
-
this.transforms.push(createZstdCompress(
|
|
301
|
-
// chunkSize: 64 * 1024, // no observed speedup
|
|
302
|
-
}));
|
|
299
|
+
this.transforms.push(createZstdCompress(zstdLevelToOptions(level)));
|
|
303
300
|
}
|
|
304
301
|
this.destination = fs2.createWriteStream(outputFilePath, {
|
|
305
302
|
// highWaterMark: 64 * 1024, // no observed speedup
|
|
@@ -310,19 +307,8 @@ export class Pipeline {
|
|
|
310
307
|
this.destination = destination;
|
|
311
308
|
await this.run();
|
|
312
309
|
}
|
|
313
|
-
async forEachLegacy(fn, opt = {}) {
|
|
314
|
-
this.transforms.push(transformMap2(fn, {
|
|
315
|
-
predicate: opt.logEvery ? _passthroughPredicate : undefined, // for the logger to work
|
|
316
|
-
...opt,
|
|
317
|
-
signal: this.abortableSignal,
|
|
318
|
-
}));
|
|
319
|
-
if (opt.logEvery) {
|
|
320
|
-
this.transforms.push(transformLogProgress(opt));
|
|
321
|
-
}
|
|
322
|
-
await this.run();
|
|
323
|
-
}
|
|
324
310
|
async forEach(fn, opt = {}) {
|
|
325
|
-
this.transforms.push(
|
|
311
|
+
this.transforms.push(transformMap(fn, {
|
|
326
312
|
predicate: opt.logEvery ? _passthroughPredicate : undefined, // for the logger to work
|
|
327
313
|
...opt,
|
|
328
314
|
signal: this.abortableSignal,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Transform } from 'node:stream';
|
|
2
|
-
import {
|
|
2
|
+
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
6
|
export function transformFilter(asyncPredicate, opt = {}) {
|
|
7
|
-
return
|
|
7
|
+
return transformMap(v => v, {
|
|
8
8
|
asyncPredicate,
|
|
9
9
|
...opt,
|
|
10
10
|
});
|
|
@@ -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 Predicate, type Promisable, SKIP, type StringMap, type UnixTimestampMillis } from '@naturalcycles/js-lib/types';
|
|
3
|
+
import { type AbortableAsyncMapper, type AsyncPredicate, END, type NumberOfSeconds, 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
|
/**
|
|
@@ -15,22 +15,16 @@ export interface TransformMapOptions<IN = any, OUT = IN> extends TransformOption
|
|
|
15
15
|
/**
|
|
16
16
|
* Number of concurrently pending promises returned by `mapper`.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
* It was recently changed up from 16, after some testing that shown that
|
|
20
|
-
* for simple low-cpu mapper functions 32 produces almost 2x throughput.
|
|
21
|
-
* For example, in scenarios like streaming a query from Datastore.
|
|
22
|
-
* UPD: changed back from 32 to 16, "to be on a safe side", as 32 sometimes
|
|
23
|
-
* causes "Datastore timeout errors".
|
|
18
|
+
* @default 16
|
|
24
19
|
*/
|
|
25
20
|
concurrency?: PositiveInteger;
|
|
26
21
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
22
|
+
* Time in seconds to gradually increase concurrency from 1 to `concurrency`.
|
|
23
|
+
* Useful for warming up connections to databases, APIs, etc.
|
|
29
24
|
*
|
|
30
|
-
*
|
|
31
|
-
* So, 64 means a total buffer of 128 (64 input and 64 output buffer).
|
|
25
|
+
* Set to 0 to disable warmup (default).
|
|
32
26
|
*/
|
|
33
|
-
|
|
27
|
+
warmupSeconds?: NumberOfSeconds;
|
|
34
28
|
/**
|
|
35
29
|
* @default THROW_IMMEDIATELY
|
|
36
30
|
*/
|
|
@@ -62,6 +56,13 @@ export interface TransformMapOptions<IN = any, OUT = IN> extends TransformOption
|
|
|
62
56
|
*/
|
|
63
57
|
signal?: AbortableSignal;
|
|
64
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Like transformMap, but with native concurrency control (no through2-concurrent dependency)
|
|
61
|
+
* and support for gradual warmup.
|
|
62
|
+
*
|
|
63
|
+
* @experimental
|
|
64
|
+
*/
|
|
65
|
+
export declare function transformMap<IN = any, OUT = IN>(mapper: AbortableAsyncMapper<IN, OUT | typeof SKIP | typeof END>, opt?: TransformMapOptions<IN, OUT>): TransformTyped<IN, OUT>;
|
|
65
66
|
export interface TransformMapStats {
|
|
66
67
|
/**
|
|
67
68
|
* True if transform was successful (didn't throw Immediate or Aggregated error).
|
|
@@ -88,19 +89,6 @@ export interface TransformMapStatsSummary extends TransformMapStats {
|
|
|
88
89
|
*/
|
|
89
90
|
extra?: StringMap<any>;
|
|
90
91
|
}
|
|
91
|
-
/**
|
|
92
|
-
* Like pMap, but for streams.
|
|
93
|
-
* Inspired by `through2`.
|
|
94
|
-
* Main feature is concurrency control (implemented via `through2-concurrent`) and convenient options.
|
|
95
|
-
* Using this allows native stream .pipe() to work and use backpressure.
|
|
96
|
-
*
|
|
97
|
-
* Only works in objectMode (due to through2Concurrent).
|
|
98
|
-
*
|
|
99
|
-
* Concurrency defaults to 16.
|
|
100
|
-
*
|
|
101
|
-
* If an Array is returned by `mapper` - it will be flattened and multiple results will be emitted from it. Tested by Array.isArray().
|
|
102
|
-
*/
|
|
103
|
-
export declare function transformMap<IN = any, OUT = IN>(mapper: AbortableAsyncMapper<IN, OUT | typeof SKIP | typeof END>, opt?: TransformMapOptions<IN, OUT>): TransformTyped<IN, OUT>;
|
|
104
92
|
/**
|
|
105
93
|
* Renders TransformMapStatsSummary into a friendly string,
|
|
106
94
|
* to be used e.g in Github Actions summary or Slack.
|
|
@@ -1,159 +1,175 @@
|
|
|
1
|
+
import { Transform } from 'node:stream';
|
|
1
2
|
import { _hc } from '@naturalcycles/js-lib';
|
|
2
|
-
import { _since } from '@naturalcycles/js-lib/datetime
|
|
3
|
+
import { _since } from '@naturalcycles/js-lib/datetime';
|
|
3
4
|
import { _anyToError, _assert, ErrorMode } from '@naturalcycles/js-lib/error';
|
|
4
5
|
import { createCommonLoggerAtLevel } from '@naturalcycles/js-lib/log';
|
|
5
|
-
import {
|
|
6
|
+
import { pDefer } from '@naturalcycles/js-lib/promise/pDefer.js';
|
|
7
|
+
import { _stringify } from '@naturalcycles/js-lib/string';
|
|
6
8
|
import { END, SKIP, } from '@naturalcycles/js-lib/types';
|
|
7
|
-
import through2Concurrent from 'through2-concurrent';
|
|
8
9
|
import { yellow } from '../../colors/colors.js';
|
|
9
10
|
import { PIPELINE_GRACEFUL_ABORT } from '../stream.util.js';
|
|
10
|
-
|
|
11
|
-
// export class TransformMap extends AbortableTransform {}
|
|
11
|
+
const WARMUP_CHECK_INTERVAL_MS = 1000;
|
|
12
12
|
/**
|
|
13
|
-
* Like
|
|
14
|
-
*
|
|
15
|
-
* Main feature is concurrency control (implemented via `through2-concurrent`) and convenient options.
|
|
16
|
-
* Using this allows native stream .pipe() to work and use backpressure.
|
|
13
|
+
* Like transformMap, but with native concurrency control (no through2-concurrent dependency)
|
|
14
|
+
* and support for gradual warmup.
|
|
17
15
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* Concurrency defaults to 16.
|
|
21
|
-
*
|
|
22
|
-
* If an Array is returned by `mapper` - it will be flattened and multiple results will be emitted from it. Tested by Array.isArray().
|
|
16
|
+
* @experimental
|
|
23
17
|
*/
|
|
24
18
|
export function transformMap(mapper, opt = {}) {
|
|
25
|
-
const { concurrency = 16,
|
|
26
|
-
|
|
27
|
-
const
|
|
19
|
+
const { concurrency: maxConcurrency = 16, warmupSeconds = 0, predicate, asyncPredicate, errorMode = ErrorMode.THROW_IMMEDIATELY, onError, onDone, metric = 'stream', signal, objectMode = true, highWaterMark = 64, } = opt;
|
|
20
|
+
const warmupMs = warmupSeconds * 1000;
|
|
21
|
+
const logger = createCommonLoggerAtLevel(opt.logger, opt.logLevel);
|
|
22
|
+
// Stats
|
|
23
|
+
let started = 0;
|
|
28
24
|
let index = -1;
|
|
29
25
|
let countOut = 0;
|
|
30
26
|
let isSettled = false;
|
|
31
27
|
let ok = true;
|
|
32
28
|
let errors = 0;
|
|
33
|
-
const collectedErrors = [];
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
const collectedErrors = [];
|
|
30
|
+
// Concurrency control - single counter, single callback for backpressure
|
|
31
|
+
let inFlight = 0;
|
|
32
|
+
let blockedCallback = null;
|
|
33
|
+
let flushBlocked = null;
|
|
34
|
+
// Warmup - cached concurrency to reduce Date.now() syscalls
|
|
35
|
+
let warmupComplete = warmupSeconds <= 0 || maxConcurrency <= 1;
|
|
36
|
+
let concurrency = warmupComplete ? maxConcurrency : 1;
|
|
37
|
+
let lastWarmupCheck = 0;
|
|
38
|
+
return new Transform({
|
|
39
|
+
objectMode,
|
|
37
40
|
readableHighWaterMark: highWaterMark,
|
|
38
41
|
writableHighWaterMark: highWaterMark,
|
|
39
|
-
async
|
|
40
|
-
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
ok: false,
|
|
45
|
-
collectedErrors,
|
|
46
|
-
countErrors: errors,
|
|
47
|
-
countIn: index + 1,
|
|
48
|
-
countOut,
|
|
49
|
-
started,
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
catch (err) {
|
|
53
|
-
logger.error(err);
|
|
54
|
-
}
|
|
55
|
-
// emit Aggregated error
|
|
56
|
-
cb(new AggregateError(collectedErrors, `transformMap resulted in ${collectedErrors.length} error(s)`));
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
// emit no error
|
|
60
|
-
try {
|
|
61
|
-
await onDone?.({
|
|
62
|
-
ok,
|
|
63
|
-
collectedErrors,
|
|
64
|
-
countErrors: errors,
|
|
65
|
-
countIn: index + 1,
|
|
66
|
-
countOut,
|
|
67
|
-
started,
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
catch (err) {
|
|
71
|
-
logger.error(err);
|
|
72
|
-
}
|
|
73
|
-
cb();
|
|
42
|
+
async transform(chunk, _, cb) {
|
|
43
|
+
// Initialize start time on first item
|
|
44
|
+
if (started === 0) {
|
|
45
|
+
started = Date.now();
|
|
46
|
+
lastWarmupCheck = started;
|
|
74
47
|
}
|
|
75
|
-
},
|
|
76
|
-
}, async function transformMapFn(chunk, _, cb) {
|
|
77
|
-
// Stop processing if isSettled (either THROW_IMMEDIATELY was fired or END received)
|
|
78
|
-
if (isSettled)
|
|
79
|
-
return cb();
|
|
80
|
-
const currentIndex = ++index;
|
|
81
|
-
try {
|
|
82
|
-
const res = await mapper(chunk, currentIndex);
|
|
83
|
-
// Check for isSettled again, as it may happen while mapper was running
|
|
84
48
|
if (isSettled)
|
|
85
49
|
return cb();
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
signal.abort(new Error(PIPELINE_GRACEFUL_ABORT));
|
|
91
|
-
return cb();
|
|
50
|
+
const currentIndex = ++index;
|
|
51
|
+
inFlight++;
|
|
52
|
+
if (!warmupComplete) {
|
|
53
|
+
updateConcurrency();
|
|
92
54
|
}
|
|
93
|
-
if
|
|
94
|
-
|
|
95
|
-
|
|
55
|
+
// Apply backpressure if at capacity, otherwise request more input
|
|
56
|
+
if (inFlight < concurrency) {
|
|
57
|
+
cb();
|
|
96
58
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
countOut++;
|
|
100
|
-
this.push(res);
|
|
101
|
-
}
|
|
59
|
+
else {
|
|
60
|
+
blockedCallback = cb;
|
|
102
61
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
62
|
+
try {
|
|
63
|
+
const res = await mapper(chunk, currentIndex);
|
|
64
|
+
if (isSettled)
|
|
65
|
+
return;
|
|
66
|
+
if (res === END) {
|
|
67
|
+
isSettled = true;
|
|
68
|
+
logger.log(`transformMap2 END received at index ${currentIndex}`);
|
|
69
|
+
_assert(signal, 'signal is required when using END');
|
|
70
|
+
signal.abort(new Error(PIPELINE_GRACEFUL_ABORT));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (res === SKIP)
|
|
74
|
+
return;
|
|
75
|
+
let shouldPush = true;
|
|
76
|
+
if (predicate) {
|
|
77
|
+
shouldPush = predicate(res, currentIndex);
|
|
78
|
+
}
|
|
79
|
+
else if (asyncPredicate) {
|
|
80
|
+
shouldPush = (await asyncPredicate(res, currentIndex)) && !isSettled;
|
|
81
|
+
}
|
|
82
|
+
if (shouldPush) {
|
|
106
83
|
countOut++;
|
|
107
84
|
this.push(res);
|
|
108
85
|
}
|
|
109
86
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
87
|
+
catch (err) {
|
|
88
|
+
logger.error(err);
|
|
89
|
+
errors++;
|
|
90
|
+
logErrorStats();
|
|
91
|
+
if (onError) {
|
|
92
|
+
try {
|
|
93
|
+
onError(_anyToError(err), chunk);
|
|
94
|
+
}
|
|
95
|
+
catch { }
|
|
96
|
+
}
|
|
97
|
+
if (errorMode === ErrorMode.THROW_IMMEDIATELY) {
|
|
98
|
+
isSettled = true;
|
|
99
|
+
ok = false;
|
|
100
|
+
await callOnDone();
|
|
101
|
+
this.destroy(_anyToError(err));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (errorMode === ErrorMode.THROW_AGGREGATED) {
|
|
105
|
+
collectedErrors.push(_anyToError(err));
|
|
106
|
+
}
|
|
113
107
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
108
|
+
finally {
|
|
109
|
+
inFlight--;
|
|
110
|
+
// Release blocked callback if we now have capacity
|
|
111
|
+
if (blockedCallback && inFlight < concurrency) {
|
|
112
|
+
const pendingCb = blockedCallback;
|
|
113
|
+
blockedCallback = null;
|
|
114
|
+
pendingCb();
|
|
115
|
+
}
|
|
116
|
+
// Trigger flush completion if all done
|
|
117
|
+
if (inFlight === 0 && flushBlocked) {
|
|
118
|
+
flushBlocked.resolve();
|
|
123
119
|
}
|
|
124
|
-
catch { }
|
|
125
120
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
// await onDone?.({
|
|
133
|
-
// ok: false,
|
|
134
|
-
// collectedErrors,
|
|
135
|
-
// countErrors: errors,
|
|
136
|
-
// countIn: index + 1,
|
|
137
|
-
// countOut,
|
|
138
|
-
// started,
|
|
139
|
-
// })
|
|
140
|
-
// } catch (err) {
|
|
141
|
-
// logger.error(err)
|
|
142
|
-
// }
|
|
143
|
-
return cb(err); // Emit error immediately
|
|
121
|
+
},
|
|
122
|
+
async flush(cb) {
|
|
123
|
+
// Wait for all in-flight operations to complete
|
|
124
|
+
if (inFlight > 0) {
|
|
125
|
+
flushBlocked = pDefer();
|
|
126
|
+
await flushBlocked;
|
|
144
127
|
}
|
|
145
|
-
|
|
146
|
-
|
|
128
|
+
logErrorStats(true);
|
|
129
|
+
await callOnDone();
|
|
130
|
+
if (collectedErrors.length) {
|
|
131
|
+
cb(new AggregateError(collectedErrors, `transformMap2 resulted in ${collectedErrors.length} error(s)`));
|
|
147
132
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
133
|
+
else {
|
|
134
|
+
cb();
|
|
135
|
+
}
|
|
136
|
+
},
|
|
151
137
|
});
|
|
138
|
+
function updateConcurrency() {
|
|
139
|
+
const now = Date.now();
|
|
140
|
+
if (now - lastWarmupCheck < WARMUP_CHECK_INTERVAL_MS)
|
|
141
|
+
return;
|
|
142
|
+
lastWarmupCheck = now;
|
|
143
|
+
const elapsed = now - started;
|
|
144
|
+
if (elapsed >= warmupMs) {
|
|
145
|
+
warmupComplete = true;
|
|
146
|
+
concurrency = maxConcurrency;
|
|
147
|
+
logger.log(`transformMap2: warmup complete in ${_since(started)}`);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const progress = elapsed / warmupMs;
|
|
151
|
+
concurrency = Math.max(1, Math.floor(1 + (maxConcurrency - 1) * progress));
|
|
152
|
+
}
|
|
152
153
|
function logErrorStats(final = false) {
|
|
153
154
|
if (!errors)
|
|
154
155
|
return;
|
|
155
156
|
logger.log(`${metric} ${final ? 'final ' : ''}errors: ${yellow(errors)}`);
|
|
156
157
|
}
|
|
158
|
+
async function callOnDone() {
|
|
159
|
+
try {
|
|
160
|
+
await onDone?.({
|
|
161
|
+
ok: collectedErrors.length === 0 && ok,
|
|
162
|
+
collectedErrors,
|
|
163
|
+
countErrors: errors,
|
|
164
|
+
countIn: index + 1,
|
|
165
|
+
countOut,
|
|
166
|
+
started,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
logger.error(err);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
157
173
|
}
|
|
158
174
|
/**
|
|
159
175
|
* Renders TransformMapStatsSummary into a friendly string,
|