@naturalcycles/nodejs-lib 12.56.1 → 12.60.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.
Files changed (86) hide show
  1. package/dist/index.d.ts +19 -18
  2. package/dist/index.js +19 -92
  3. package/dist/log/log.util.d.ts +4 -0
  4. package/dist/log/log.util.js +11 -0
  5. package/dist/stream/ndjson/ndjsonMap.d.ts +2 -2
  6. package/dist/stream/ndjson/ndjsonMap.js +4 -3
  7. package/dist/stream/ndjson/ndjsonStreamForEach.d.ts +2 -2
  8. package/dist/stream/ndjson/transformJsonParse.js +3 -3
  9. package/dist/stream/ndjson/transformToNDJson.js +2 -2
  10. package/dist/stream/pipeline/pipeline.d.ts +25 -3
  11. package/dist/stream/pipeline/pipeline.js +76 -9
  12. package/dist/stream/readable/readableCreate.d.ts +8 -0
  13. package/dist/stream/readable/readableCreate.js +9 -1
  14. package/dist/stream/readable/readableForEach.d.ts +2 -2
  15. package/dist/stream/readable/readableFromArray.d.ts +2 -2
  16. package/dist/stream/readable/readableFromArray.js +17 -13
  17. package/dist/stream/readable/readableMap.d.ts +2 -2
  18. package/dist/stream/readable/readableMap.js +22 -17
  19. package/dist/stream/sizeStack.d.ts +9 -0
  20. package/dist/stream/sizeStack.js +48 -0
  21. package/dist/stream/stream.util.d.ts +4 -0
  22. package/dist/stream/stream.util.js +24 -0
  23. package/dist/stream/transform/transformBuffer.js +1 -1
  24. package/dist/stream/transform/transformFilter.d.ts +3 -4
  25. package/dist/stream/transform/transformFilter.js +5 -20
  26. package/dist/stream/transform/transformLimit.d.ts +36 -1
  27. package/dist/stream/transform/transformLimit.js +33 -15
  28. package/dist/stream/transform/transformLogProgress.d.ts +22 -1
  29. package/dist/stream/transform/transformLogProgress.js +38 -20
  30. package/dist/stream/transform/transformMap.d.ts +4 -10
  31. package/dist/stream/transform/transformMap.js +52 -64
  32. package/dist/stream/transform/transformMapSimple.d.ts +2 -1
  33. package/dist/stream/transform/transformMapSimple.js +3 -3
  34. package/dist/stream/transform/transformMapSync.d.ts +7 -4
  35. package/dist/stream/transform/transformMapSync.js +30 -24
  36. package/dist/stream/transform/transformNoOp.js +1 -1
  37. package/dist/stream/transform/transformTap.d.ts +5 -2
  38. package/dist/stream/transform/transformTap.js +5 -4
  39. package/dist/stream/transform/transformToArray.js +1 -1
  40. package/dist/stream/transform/transformToString.js +2 -2
  41. package/dist/stream/transform/worker/transformMultiThreaded.js +1 -1
  42. package/dist/stream/transform/worker/workerClassProxy.js +1 -0
  43. package/dist/stream/writable/writableFork.d.ts +2 -0
  44. package/dist/stream/writable/writableFork.js +3 -1
  45. package/dist/stream/writable/writableLimit.d.ts +9 -0
  46. package/dist/stream/writable/writableLimit.js +29 -0
  47. package/dist/stream/writable/writablePushToArray.js +1 -1
  48. package/dist/stream/writable/writableVoid.d.ts +8 -1
  49. package/dist/stream/writable/writableVoid.js +6 -2
  50. package/dist/util/zip.util.d.ts +10 -2
  51. package/dist/util/zip.util.js +10 -3
  52. package/package.json +1 -1
  53. package/src/index.ts +17 -156
  54. package/src/log/log.util.ts +9 -0
  55. package/src/stream/ndjson/ndjsonMap.ts +7 -5
  56. package/src/stream/ndjson/ndjsonStreamForEach.ts +2 -2
  57. package/src/stream/ndjson/transformJsonParse.ts +3 -3
  58. package/src/stream/ndjson/transformToNDJson.ts +2 -2
  59. package/src/stream/pipeline/pipeline.ts +102 -9
  60. package/src/stream/readable/readableCreate.ts +9 -1
  61. package/src/stream/readable/readableForEach.ts +2 -2
  62. package/src/stream/readable/readableFromArray.ts +18 -21
  63. package/src/stream/readable/readableMap.ts +24 -21
  64. package/src/stream/sizeStack.ts +56 -0
  65. package/src/stream/stream.util.ts +29 -0
  66. package/src/stream/transform/transformBuffer.ts +1 -1
  67. package/src/stream/transform/transformFilter.ts +6 -20
  68. package/src/stream/transform/transformLimit.ts +71 -19
  69. package/src/stream/transform/transformLogProgress.ts +78 -26
  70. package/src/stream/transform/transformMap.ts +74 -94
  71. package/src/stream/transform/transformMapSimple.ts +6 -4
  72. package/src/stream/transform/transformMapSync.ts +45 -28
  73. package/src/stream/transform/transformNoOp.ts +1 -1
  74. package/src/stream/transform/transformTap.ts +11 -6
  75. package/src/stream/transform/transformToArray.ts +1 -1
  76. package/src/stream/transform/transformToString.ts +2 -2
  77. package/src/stream/transform/worker/transformMultiThreaded.ts +1 -1
  78. package/src/stream/transform/worker/workerClassProxy.js +1 -0
  79. package/src/stream/writable/writableFork.ts +3 -1
  80. package/src/stream/writable/writableLimit.ts +28 -0
  81. package/src/stream/writable/writablePushToArray.ts +1 -1
  82. package/src/stream/writable/writableVoid.ts +14 -2
  83. package/src/util/zip.util.ts +11 -3
  84. package/dist/stream/transform/legacy/transformMap.d.ts +0 -17
  85. package/dist/stream/transform/legacy/transformMap.js +0 -94
  86. package/src/stream/transform/legacy/transformMap.ts +0 -133
@@ -1,24 +1,29 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.readableMap = void 0;
4
- const index_1 = require("../../index");
4
+ const stream_1 = require("stream");
5
+ const js_lib_1 = require("@naturalcycles/js-lib");
5
6
  function readableMap(readable, mapper) {
6
- const out = (0, index_1.readableCreate)();
7
- void (async () => {
8
- try {
9
- let index = 0;
10
- for await (const item of readable) {
11
- const v = await mapper(item, index++);
12
- out.push(v);
7
+ let i = -1;
8
+ // todo: check if we need to handle errors somehow specifically
9
+ return readable.pipe(new stream_1.Transform({
10
+ objectMode: true,
11
+ async transform(chunk, _enc, cb) {
12
+ try {
13
+ const r = await mapper(chunk, ++i);
14
+ if (r === js_lib_1.SKIP) {
15
+ cb();
16
+ }
17
+ else {
18
+ // _assert(r !== END, `readableMap END not supported`)
19
+ cb(null, r);
20
+ }
13
21
  }
14
- // We're done
15
- out.push(null);
16
- }
17
- catch (err) {
18
- console.error(err);
19
- out.emit('error', err);
20
- }
21
- })();
22
- return out;
22
+ catch (err) {
23
+ console.error(err);
24
+ cb(err);
25
+ }
26
+ },
27
+ }));
23
28
  }
24
29
  exports.readableMap = readableMap;
@@ -0,0 +1,9 @@
1
+ import { CommonLogger, NumberStack } from '@naturalcycles/js-lib';
2
+ export declare class SizeStack extends NumberStack {
3
+ name: string;
4
+ constructor(name: string, size: number);
5
+ total: number;
6
+ push(item: any): this;
7
+ getStats(): string;
8
+ static countItem(item: any, logger: CommonLogger, sizes?: SizeStack, sizesZipped?: SizeStack): Promise<void>;
9
+ }
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SizeStack = void 0;
4
+ const js_lib_1 = require("@naturalcycles/js-lib");
5
+ const colors_1 = require("../colors");
6
+ const zip_util_1 = require("../util/zip.util");
7
+ class SizeStack extends js_lib_1.NumberStack {
8
+ constructor(name, size) {
9
+ super(size);
10
+ this.name = name;
11
+ this.total = 0;
12
+ }
13
+ push(item) {
14
+ this.total += item;
15
+ return super.push(item);
16
+ }
17
+ getStats() {
18
+ const pcs = this.percentiles([50, 90]);
19
+ return [
20
+ this.name,
21
+ 'avg',
22
+ (0, colors_1.yellow)((0, js_lib_1._hb)(this.avg())),
23
+ 'p50',
24
+ (0, colors_1.yellow)((0, js_lib_1._hb)(pcs[50])),
25
+ 'p90',
26
+ (0, colors_1.yellow)((0, js_lib_1._hb)(pcs[90])),
27
+ 'total',
28
+ (0, colors_1.yellow)((0, js_lib_1._hb)(this.total)),
29
+ ].join(' ');
30
+ }
31
+ static async countItem(item, logger, sizes, sizesZipped) {
32
+ if (!sizes)
33
+ return;
34
+ // try-catch, because we don't want to fail the pipeline on logProgress
35
+ try {
36
+ const buf = Buffer.from(JSON.stringify(item));
37
+ sizes.push(buf.byteLength);
38
+ if (sizesZipped) {
39
+ const { byteLength } = await (0, zip_util_1.gzipBuffer)(buf);
40
+ sizesZipped.push(byteLength);
41
+ }
42
+ }
43
+ catch (err) {
44
+ logger.warn(`transformLogProgress failed to JSON.stringify the chunk: ${err.message}`);
45
+ }
46
+ }
47
+ }
48
+ exports.SizeStack = SizeStack;
@@ -0,0 +1,4 @@
1
+ /// <reference types="node" />
2
+ import { Readable } from 'stream';
3
+ import { CommonLogger } from '@naturalcycles/js-lib';
4
+ export declare function pipelineClose(name: string, readableDownstream: Readable, sourceReadable: Readable | undefined, streamDone: Promise<void> | undefined, logger: CommonLogger): void;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.pipelineClose = void 0;
4
+ function pipelineClose(name, readableDownstream, sourceReadable, streamDone, logger) {
5
+ readableDownstream.push(null); // this closes the stream, so downstream Readable will receive `end` and won't write anything
6
+ if (!sourceReadable) {
7
+ logger.warn(`${name} sourceReadable is not provided, readable stream will not be stopped`);
8
+ }
9
+ else {
10
+ logger.log(`${name} is calling readable.unpipe() to pause the stream`);
11
+ sourceReadable.unpipe(); // it is expected to pause the stream
12
+ if (!streamDone) {
13
+ logger.log(`${name} streamDone is not provided, will do readable.destroy right away`);
14
+ sourceReadable.destroy();
15
+ }
16
+ else {
17
+ void streamDone.then(() => {
18
+ logger.log(`${name} streamDone, calling readable.destroy()`);
19
+ sourceReadable.destroy(); // this throws ERR_STREAM_PREMATURE_CLOSE
20
+ });
21
+ }
22
+ }
23
+ }
24
+ exports.pipelineClose = pipelineClose;
@@ -13,7 +13,7 @@ function transformBuffer(opt) {
13
13
  return new stream_1.Transform({
14
14
  objectMode: true,
15
15
  ...opt,
16
- transform(chunk, _encoding, cb) {
16
+ transform(chunk, _, cb) {
17
17
  buf.push(chunk);
18
18
  if (buf.length >= batchSize) {
19
19
  cb(null, buf);
@@ -1,11 +1,10 @@
1
1
  import { AsyncPredicate, Predicate } from '@naturalcycles/js-lib';
2
2
  import { TransformOptions, TransformTyped } from '../stream.model';
3
+ import { TransformMapOptions } from './transformMap';
3
4
  /**
4
- * Note, that currently it's NOT concurrent! (concurrency = 1)
5
- * So, it's recommended to use transformMap instead, that is both concurrent and has
6
- * filtering feature by default.
5
+ * Just a convenience wrapper around `transformMap` that has built-in predicate filtering support.
7
6
  */
8
- export declare function transformFilter<IN = any>(predicate: AsyncPredicate<IN>, opt?: TransformOptions): TransformTyped<IN, IN>;
7
+ export declare function transformFilter<IN = any>(predicate: AsyncPredicate<IN>, opt?: TransformMapOptions): TransformTyped<IN, IN>;
9
8
  /**
10
9
  * Sync version of `transformFilter`
11
10
  */
@@ -2,29 +2,14 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.transformFilterSync = exports.transformFilter = void 0;
4
4
  const stream_1 = require("stream");
5
+ const transformMap_1 = require("./transformMap");
5
6
  /**
6
- * Note, that currently it's NOT concurrent! (concurrency = 1)
7
- * So, it's recommended to use transformMap instead, that is both concurrent and has
8
- * filtering feature by default.
7
+ * Just a convenience wrapper around `transformMap` that has built-in predicate filtering support.
9
8
  */
10
9
  function transformFilter(predicate, opt = {}) {
11
- let index = 0;
12
- return new stream_1.Transform({
13
- objectMode: true,
10
+ return (0, transformMap_1.transformMap)(v => v, {
11
+ predicate,
14
12
  ...opt,
15
- async transform(chunk, _encoding, cb) {
16
- try {
17
- if (await predicate(chunk, index++)) {
18
- cb(null, chunk); // pass through
19
- }
20
- else {
21
- cb(); // signal that we've finished processing, but emit no output here
22
- }
23
- }
24
- catch (err) {
25
- cb(err);
26
- }
27
- },
28
13
  });
29
14
  }
30
15
  exports.transformFilter = transformFilter;
@@ -36,7 +21,7 @@ function transformFilterSync(predicate, opt = {}) {
36
21
  return new stream_1.Transform({
37
22
  objectMode: true,
38
23
  ...opt,
39
- async transform(chunk, _encoding, cb) {
24
+ transform(chunk, _, cb) {
40
25
  try {
41
26
  if (predicate(chunk, index++)) {
42
27
  cb(null, chunk); // pass through
@@ -1,5 +1,40 @@
1
+ /// <reference types="node" />
2
+ import { Readable } from 'stream';
3
+ import { CommonLogger } from '@naturalcycles/js-lib';
4
+ import { AbortableTransform } from '../../index';
1
5
  import { TransformOptions, TransformTyped } from '../stream.model';
6
+ export interface TransformLimitOptions extends TransformOptions {
7
+ /**
8
+ * Nullish value (e.g 0 or undefined) would mean "no limit"
9
+ */
10
+ limit?: number;
11
+ /**
12
+ * If provided (recommended!) - it will call readable.destroy() on limit.
13
+ * Without it - it will only stop the downstream consumers, but won't stop
14
+ * the Readable ("source" of the stream).
15
+ * It is almost always crucial to stop the Source too, so, please provide the Readable here!
16
+ */
17
+ sourceReadable?: Readable;
18
+ /**
19
+ * Please provide it (a Promise that resolves when the Stream is done, e.g finished consuming things)
20
+ * to be able to wait for Consumers before calling `readable.destroy`.
21
+ * Has no effect if `readable` is not provided.
22
+ */
23
+ streamDone?: Promise<void>;
24
+ logger?: CommonLogger;
25
+ /**
26
+ * Set to true to enable additional debug messages, e.g it'll log
27
+ * when readable still emits values after the limit is reached.
28
+ */
29
+ debug?: boolean;
30
+ }
31
+ /**
32
+ * Class only exists to be able to do `instanceof TransformLimit`
33
+ * and to set sourceReadable+streamDone to it in `_pipeline`.
34
+ */
35
+ export declare class TransformLimit extends AbortableTransform {
36
+ }
2
37
  /**
3
38
  * 0 or falsy value means "no limit"
4
39
  */
5
- export declare function transformLimit<IN>(limit?: number, opt?: TransformOptions): TransformTyped<IN, IN>;
40
+ export declare function transformLimit<IN>(opt?: TransformLimitOptions): TransformTyped<IN, IN>;
@@ -1,28 +1,46 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.transformLimit = void 0;
4
- const stream_1 = require("stream");
3
+ exports.transformLimit = exports.TransformLimit = void 0;
4
+ const index_1 = require("../../index");
5
+ const stream_util_1 = require("../stream.util");
6
+ /**
7
+ * Class only exists to be able to do `instanceof TransformLimit`
8
+ * and to set sourceReadable+streamDone to it in `_pipeline`.
9
+ */
10
+ class TransformLimit extends index_1.AbortableTransform {
11
+ }
12
+ exports.TransformLimit = TransformLimit;
5
13
  /**
6
14
  * 0 or falsy value means "no limit"
7
15
  */
8
- function transformLimit(limit, opt = {}) {
9
- let index = 0;
16
+ function transformLimit(opt = {}) {
17
+ const { logger = console, limit, debug } = opt;
18
+ if (!limit) {
19
+ // No limit - returning pass-through transform
20
+ return (0, index_1.transformNoOp)();
21
+ }
22
+ let i = 0; // so we start first chunk with 1
10
23
  let ended = false;
11
- return new stream_1.Transform({
24
+ return new TransformLimit({
12
25
  objectMode: true,
13
26
  ...opt,
14
- transform(chunk, _encoding, cb) {
15
- index++;
16
- if (!ended) {
17
- cb(null, chunk); // pass through the item
27
+ transform(chunk, _, cb) {
28
+ i++;
29
+ if (i === limit) {
30
+ ended = true;
31
+ logger.log(`transformLimit of ${limit} reached`);
32
+ this.push(chunk);
33
+ (0, stream_util_1.pipelineClose)('transformLimit', this, opt.sourceReadable || this.sourceReadable, opt.streamDone || this.streamDone, logger);
34
+ cb(); // after pause
18
35
  }
19
- else {
20
- cb(null); // pass-through empty
36
+ else if (!ended) {
37
+ cb(null, chunk);
21
38
  }
22
- if (limit && index === limit) {
23
- ended = true;
24
- console.log(`transformLimit: limit of ${limit} reached`);
25
- // this.emit('end') // this makes it "halt" on Node 14 lts
39
+ else {
40
+ if (debug)
41
+ logger.log(`transformLimit.transform after limit`, i);
42
+ // If we ever HANG (don't call cb) - Node will do process.exit(0) to us
43
+ cb(); // ended, don't emit anything
26
44
  }
27
45
  },
28
46
  });
@@ -1,4 +1,4 @@
1
- import { AnyObject } from '@naturalcycles/js-lib';
1
+ import { AnyObject, CommonLogger } from '@naturalcycles/js-lib';
2
2
  import { TransformOptions, TransformTyped } from '../stream.model';
3
3
  export interface TransformLogProgressOptions<IN = any> extends TransformOptions {
4
4
  /**
@@ -69,6 +69,7 @@ export interface TransformLogProgressOptions<IN = any> extends TransformOptions
69
69
  * @default 1000
70
70
  */
71
71
  logEvery?: number;
72
+ logger?: CommonLogger;
72
73
  /**
73
74
  * Function to return extra properties to the "progress object".
74
75
  *
@@ -83,6 +84,26 @@ export interface TransformLogProgressOptions<IN = any> extends TransformOptions
83
84
  * Defaults to 1.
84
85
  */
85
86
  batchSize?: number;
87
+ /**
88
+ * Experimental logging of item (shunk) sizes, when json-stringified.
89
+ *
90
+ * Defaults to false.
91
+ *
92
+ * @experimental
93
+ */
94
+ logSizes?: boolean;
95
+ /**
96
+ * How many last item sizes to keep in a buffer, to calculate stats (p50, p90, avg, etc).
97
+ * Defaults to 100_000.
98
+ * Cannot be Infinity.
99
+ */
100
+ logSizesBuffer?: number;
101
+ /**
102
+ * Works in addition to `logSizes`. Adds "zipped sizes".
103
+ *
104
+ * @experimental
105
+ */
106
+ logZippedSizes?: boolean;
86
107
  }
87
108
  /**
88
109
  * Pass-through transform that optionally logs progress.
@@ -7,6 +7,7 @@ const js_lib_1 = require("@naturalcycles/js-lib");
7
7
  const time_lib_1 = require("@naturalcycles/time-lib");
8
8
  const colors_1 = require("../../colors");
9
9
  const colors_2 = require("../../colors/colors");
10
+ const sizeStack_1 = require("../sizeStack");
10
11
  const inspectOpt = {
11
12
  colors: colors_2.hasColors,
12
13
  breakLength: 300,
@@ -15,7 +16,7 @@ const inspectOpt = {
15
16
  * Pass-through transform that optionally logs progress.
16
17
  */
17
18
  function transformLogProgress(opt = {}) {
18
- const { metric = 'progress', heapTotal: logHeapTotal = false, heapUsed: logHeapUsed = false, rss: logRss = true, peakRSS: logPeakRSS = true, logRPS = true, logEvery = 1000, batchSize = 1, extra, } = opt;
19
+ const { metric = 'progress', heapTotal: logHeapTotal = false, heapUsed: logHeapUsed = false, rss: logRss = true, peakRSS: logPeakRSS = true, logRPS = true, logEvery = 1000, logSizes = false, logSizesBuffer = 100000, logZippedSizes = false, batchSize = 1, extra, logger = console, } = opt;
19
20
  const logProgress = opt.logProgress !== false && logEvery !== 0; // true by default
20
21
  const logEvery10 = logEvery * 10;
21
22
  const started = Date.now();
@@ -24,13 +25,19 @@ function transformLogProgress(opt = {}) {
24
25
  let processedLastSecond = 0;
25
26
  let progress = 0;
26
27
  let peakRSS = 0;
28
+ const sizes = logSizes ? new sizeStack_1.SizeStack('json', logSizesBuffer) : undefined;
29
+ const sizesZipped = logZippedSizes ? new sizeStack_1.SizeStack('json.gz', logSizesBuffer) : undefined;
27
30
  logStats(); // initial
28
31
  return new stream_1.Transform({
29
32
  objectMode: true,
30
33
  ...opt,
31
- transform(chunk, _encoding, cb) {
34
+ transform(chunk, _, cb) {
32
35
  progress++;
33
36
  processedLastSecond++;
37
+ if (sizes) {
38
+ // Check it, cause gzipping might be delayed here..
39
+ void sizeStack_1.SizeStack.countItem(chunk, logger, sizes, sizesZipped);
40
+ }
34
41
  if (logProgress && progress % logEvery === 0) {
35
42
  logStats(chunk, false, progress % logEvery10 === 0);
36
43
  }
@@ -54,32 +61,43 @@ function transformLogProgress(opt = {}) {
54
61
  const rps10 = Math.round(sma.push(lastRPS));
55
62
  if (mem.rss > peakRSS)
56
63
  peakRSS = mem.rss;
57
- console.log((0, util_1.inspect)({
64
+ const o = {
58
65
  [final ? `${metric}_final` : metric]: batchedProgress,
59
- ...(extra ? extra(chunk, progress) : {}),
60
- ...(logHeapUsed ? { heapUsed: (0, js_lib_1._mb)(mem.heapUsed) } : {}),
61
- ...(logHeapTotal ? { heapTotal: (0, js_lib_1._mb)(mem.heapTotal) } : {}),
62
- ...(logRss ? { rss: (0, js_lib_1._mb)(mem.rss) } : {}),
63
- ...(logPeakRSS ? { peakRSS: (0, js_lib_1._mb)(peakRSS) } : {}),
64
- ...(opt.rssMinusHeap ? { rssMinusHeap: (0, js_lib_1._mb)(mem.rss - mem.heapTotal) } : {}),
65
- ...(opt.external ? { external: (0, js_lib_1._mb)(mem.external) } : {}),
66
- ...(opt.arrayBuffers ? { arrayBuffers: (0, js_lib_1._mb)(mem.arrayBuffers || 0) } : {}),
67
- ...(logRPS
68
- ? {
69
- rps10,
70
- rpsTotal,
71
- }
72
- : {}),
73
- }, inspectOpt));
66
+ };
67
+ if (extra)
68
+ Object.assign(o, extra(chunk, progress));
69
+ if (logHeapUsed)
70
+ o.heapUsed = (0, js_lib_1._mb)(mem.heapUsed);
71
+ if (logHeapTotal)
72
+ o.heapTotal = (0, js_lib_1._mb)(mem.heapTotal);
73
+ if (logRss)
74
+ o.rss = (0, js_lib_1._mb)(mem.rss);
75
+ if (logPeakRSS)
76
+ o.peakRSS = (0, js_lib_1._mb)(peakRSS);
77
+ if (opt.rssMinusHeap)
78
+ o.rssMinusHeap = (0, js_lib_1._mb)(mem.rss - mem.heapTotal);
79
+ if (opt.external)
80
+ o.external = (0, js_lib_1._mb)(mem.external);
81
+ if (opt.arrayBuffers)
82
+ o.arrayBuffers = (0, js_lib_1._mb)(mem.arrayBuffers || 0);
83
+ if (logRPS)
84
+ Object.assign(o, { rps10, rpsTotal });
85
+ logger.log((0, util_1.inspect)(o, inspectOpt));
86
+ if (sizes?.items.length) {
87
+ logger.log(sizes.getStats());
88
+ if (sizesZipped?.items.length) {
89
+ logger.log(sizesZipped.getStats());
90
+ }
91
+ }
74
92
  if (tenx) {
75
93
  let perHour = Math.round((batchedProgress * 1000 * 60 * 60) / (now - started)) || 0;
76
94
  if (perHour > 900) {
77
95
  perHour = Math.round(perHour / 1000) + 'K';
78
96
  }
79
- console.log(`${(0, colors_1.dimGrey)((0, time_lib_1.dayjs)().toPretty())} ${(0, colors_1.white)(metric)} took ${(0, colors_1.yellow)((0, js_lib_1._since)(started))} so far to process ${(0, colors_1.yellow)(batchedProgress)} rows, ~${(0, colors_1.yellow)(perHour)}/hour`);
97
+ logger.log(`${(0, colors_1.dimGrey)((0, time_lib_1.dayjs)().toPretty())} ${(0, colors_1.white)(metric)} took ${(0, colors_1.yellow)((0, js_lib_1._since)(started))} so far to process ${(0, colors_1.yellow)(batchedProgress)} rows, ~${(0, colors_1.yellow)(perHour)}/hour`);
80
98
  }
81
99
  else if (final) {
82
- console.log(`${(0, colors_1.boldWhite)(metric)} took ${(0, colors_1.yellow)((0, js_lib_1._since)(started))} to process ${(0, colors_1.yellow)(batchedProgress)} rows with total RPS of ${(0, colors_1.yellow)(rpsTotal)}`);
100
+ logger.log(`${(0, colors_1.boldWhite)(metric)} took ${(0, colors_1.yellow)((0, js_lib_1._since)(started))} to process ${(0, colors_1.yellow)(batchedProgress)} rows with total RPS of ${(0, colors_1.yellow)(rpsTotal)}`);
83
101
  }
84
102
  }
85
103
  }
@@ -1,4 +1,4 @@
1
- import { AsyncMapper, AsyncPredicate, CommonLogger, ErrorMode } from '@naturalcycles/js-lib';
1
+ import { AbortableAsyncMapper, AsyncPredicate, CommonLogger, ErrorMode } from '@naturalcycles/js-lib';
2
2
  import { TransformTyped } from '../stream.model';
3
3
  export interface TransformMapOptions<IN = any, OUT = IN> {
4
4
  /**
@@ -11,9 +11,8 @@ export interface TransformMapOptions<IN = any, OUT = IN> {
11
11
  * Predicate to filter outgoing results (after mapper).
12
12
  * Allows to not emit all results.
13
13
  *
14
- * Set to `r => r` (passthrough predicate) to pass ANY value (including undefined/null)
15
- *
16
- * @default to filter out undefined/null values, but pass anything else
14
+ * Defaults to "pass everything" (including null, undefined, etc).
15
+ * Simpler way to exclude certain cases is to return SKIP symbol from the mapper.
17
16
  */
18
17
  predicate?: AsyncPredicate<OUT>;
19
18
  /**
@@ -37,13 +36,8 @@ export interface TransformMapOptions<IN = any, OUT = IN> {
37
36
  * @default `stream`
38
37
  */
39
38
  metric?: string;
40
- /**
41
- * If defined - called BEFORE `final()` callback is called.
42
- */
43
- beforeFinal?: () => any;
44
39
  logger?: CommonLogger;
45
40
  }
46
- export declare function notNullishPredicate(item: any): boolean;
47
41
  /**
48
42
  * Like pMap, but for streams.
49
43
  * Inspired by `through2`.
@@ -56,4 +50,4 @@ export declare function notNullishPredicate(item: any): boolean;
56
50
  *
57
51
  * If an Array is returned by `mapper` - it will be flattened and multiple results will be emitted from it. Tested by Array.isArray().
58
52
  */
59
- export declare function transformMap<IN = any, OUT = IN>(mapper: AsyncMapper<IN, OUT>, opt?: TransformMapOptions<IN, OUT>): TransformTyped<IN, OUT>;
53
+ export declare function transformMap<IN = any, OUT = IN>(mapper: AbortableAsyncMapper<IN, OUT>, opt?: TransformMapOptions<IN, OUT>): TransformTyped<IN, OUT>;
@@ -1,13 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.transformMap = exports.notNullishPredicate = void 0;
4
- const stream_1 = require("stream");
3
+ exports.transformMap = void 0;
5
4
  const js_lib_1 = require("@naturalcycles/js-lib");
5
+ const through2Concurrent = require("through2-concurrent");
6
6
  const colors_1 = require("../../colors");
7
- function notNullishPredicate(item) {
8
- return item !== undefined && item !== null;
9
- }
10
- exports.notNullishPredicate = notNullishPredicate;
7
+ const stream_util_1 = require("../stream.util");
8
+ // doesn't work, cause here we don't construct our Transform instance ourselves
9
+ // export class TransformMap extends AbortableTransform {}
11
10
  /**
12
11
  * Like pMap, but for streams.
13
12
  * Inspired by `through2`.
@@ -21,80 +20,69 @@ exports.notNullishPredicate = notNullishPredicate;
21
20
  * If an Array is returned by `mapper` - it will be flattened and multiple results will be emitted from it. Tested by Array.isArray().
22
21
  */
23
22
  function transformMap(mapper, opt = {}) {
24
- const { concurrency = 16, predicate = notNullishPredicate, errorMode = js_lib_1.ErrorMode.THROW_IMMEDIATELY, flattenArrayOutput, onError, beforeFinal, metric = 'stream', logger = console, } = opt;
23
+ const { concurrency = 16, predicate, // we now default to "no predicate" (meaning pass-everything)
24
+ errorMode = js_lib_1.ErrorMode.THROW_IMMEDIATELY, flattenArrayOutput, onError, metric = 'stream', logger = console, } = opt;
25
25
  let index = -1;
26
- let isRejected = false;
26
+ let isSettled = false;
27
27
  let errors = 0;
28
28
  const collectedErrors = []; // only used if errorMode == THROW_AGGREGATED
29
- const q = new js_lib_1.PQueue({
30
- concurrency,
31
- resolveOn: 'start',
32
- // debug: true,
33
- });
34
- return new stream_1.Transform({
35
- objectMode: true,
29
+ return through2Concurrent.obj({
30
+ maxConcurrency: concurrency,
36
31
  async final(cb) {
37
- // console.log('transformMap final', {index}, q.inFlight, q.queueSize)
38
- // wait for the current inFlight jobs to complete and push their results
39
- await q.onIdle();
40
- logErrorStats(logger, true);
41
- await beforeFinal?.(); // call beforeFinal if defined
32
+ // console.log('transformMap final')
33
+ logErrorStats(true);
42
34
  if (collectedErrors.length) {
43
35
  // emit Aggregated error
44
- // For the same reason, magically, let's not call `cb`, but emit an error event instead
45
- // this.emit('error', new AggregatedError(collectedErrors))
46
36
  cb(new js_lib_1.AggregatedError(collectedErrors));
47
37
  }
48
38
  else {
49
39
  // emit no error
50
- // It is truly a mistery, but calling cb() here was causing ERR_MULTIPLE_CALLBACK ?!
51
- // Commenting it out seems to work ?!
52
- // ?!
53
- // cb()
40
+ cb();
54
41
  }
55
42
  },
56
- async transform(chunk, _encoding, cb) {
57
- index++;
58
- // console.log('transform', {index})
59
- // Stop processing if THROW_IMMEDIATELY mode is used
60
- if (isRejected && errorMode === js_lib_1.ErrorMode.THROW_IMMEDIATELY)
61
- return cb();
62
- // It resolves when it is successfully STARTED execution.
63
- // If it's queued instead - it'll wait and resolve only upon START.
64
- await q.push(async () => {
65
- try {
66
- const currentIndex = index; // because we need to pass it to 2 functions - mapper and predicate. Refers to INPUT index (since it may return multiple outputs)
67
- const res = await mapper(chunk, currentIndex);
68
- const passedResults = await (0, js_lib_1.pFilter)(flattenArrayOutput && Array.isArray(res) ? res : [res], async (r) => await predicate(r, currentIndex));
69
- passedResults.forEach(r => this.push(r));
70
- }
71
- catch (err) {
72
- logger.error(err);
73
- errors++;
74
- logErrorStats(logger);
75
- if (onError) {
76
- try {
77
- onError(err, chunk);
78
- }
79
- catch { }
80
- }
81
- if (errorMode === js_lib_1.ErrorMode.THROW_IMMEDIATELY) {
82
- isRejected = true;
83
- // Emit error immediately
84
- // return cb(err as Error)
85
- return this.emit('error', err);
86
- }
87
- if (errorMode === js_lib_1.ErrorMode.THROW_AGGREGATED) {
88
- collectedErrors.push(err);
89
- }
43
+ }, async function transformMapFn(chunk, _, cb) {
44
+ // Stop processing if isSettled (either THROW_IMMEDIATELY was fired or END received)
45
+ if (isSettled)
46
+ return cb();
47
+ const currentIndex = ++index;
48
+ try {
49
+ const res = await mapper(chunk, currentIndex);
50
+ const passedResults = await (0, js_lib_1.pFilter)(flattenArrayOutput && Array.isArray(res) ? res : [res], async (r) => {
51
+ if (r === js_lib_1.END) {
52
+ isSettled = true; // will be checked later
53
+ return false;
90
54
  }
55
+ return r !== js_lib_1.SKIP && (!predicate || (await predicate(r, currentIndex)));
91
56
  });
92
- // Resolved, which means it STARTED processing
93
- // This means we can take more load
57
+ passedResults.forEach(r => this.push(r));
58
+ if (isSettled) {
59
+ logger.log(`transformMap END received at index ${currentIndex}`);
60
+ (0, stream_util_1.pipelineClose)('transformMap', this, this.sourceReadable, this.streamDone, logger);
61
+ }
62
+ cb(); // done processing
63
+ }
64
+ catch (err) {
65
+ logger.error(err);
66
+ errors++;
67
+ logErrorStats();
68
+ if (onError) {
69
+ try {
70
+ onError(err, chunk);
71
+ }
72
+ catch { }
73
+ }
74
+ if (errorMode === js_lib_1.ErrorMode.THROW_IMMEDIATELY) {
75
+ isSettled = true;
76
+ return cb(err); // Emit error immediately
77
+ }
78
+ if (errorMode === js_lib_1.ErrorMode.THROW_AGGREGATED) {
79
+ collectedErrors.push(err);
80
+ }
81
+ // Tell input stream that we're done processing, but emit nothing to output - not error nor result
94
82
  cb();
95
- },
83
+ }
96
84
  });
97
- function logErrorStats(logger, final = false) {
85
+ function logErrorStats(final = false) {
98
86
  if (!errors)
99
87
  return;
100
88
  logger.log(`${metric} ${final ? 'final ' : ''}errors: ${(0, colors_1.yellow)(errors)}`);
@@ -1,4 +1,4 @@
1
- import { ErrorMode, Mapper } from '@naturalcycles/js-lib';
1
+ import { CommonLogger, ErrorMode, Mapper } from '@naturalcycles/js-lib';
2
2
  import { TransformTyped } from '../stream.model';
3
3
  export interface TransformMapSimpleOptions {
4
4
  /**
@@ -7,6 +7,7 @@ export interface TransformMapSimpleOptions {
7
7
  * @default ErrorMode.THROW_IMMEDIATELY
8
8
  */
9
9
  errorMode?: ErrorMode.THROW_IMMEDIATELY | ErrorMode.SUPPRESS;
10
+ logger?: CommonLogger;
10
11
  }
11
12
  /**
12
13
  * Simplest version of `transformMap`.