@naturalcycles/nodejs-lib 12.54.0 → 12.55.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.
@@ -0,0 +1,17 @@
1
+ import { AsyncMapper } from '@naturalcycles/js-lib';
2
+ import { TransformTyped } from '../../stream.model';
3
+ import { TransformMapOptions } from '../transformMap';
4
+ export declare function notNullishPredicate(item: any): boolean;
5
+ /**
6
+ * Like pMap, but for streams.
7
+ * Inspired by `through2`.
8
+ * Main feature is concurrency control (implemented via `through2-concurrent`) and convenient options.
9
+ * Using this allows native stream .pipe() to work and use backpressure.
10
+ *
11
+ * Only works in objectMode (due to through2Concurrent).
12
+ *
13
+ * Concurrency defaults to 16.
14
+ *
15
+ * If an Array is returned by `mapper` - it will be flattened and multiple results will be emitted from it. Tested by Array.isArray().
16
+ */
17
+ export declare function transformMap2<IN = any, OUT = IN>(mapper: AsyncMapper<IN, OUT>, opt?: TransformMapOptions<IN, OUT>): TransformTyped<IN, OUT>;
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.transformMap2 = exports.notNullishPredicate = void 0;
4
+ const stream_1 = require("stream");
5
+ const js_lib_1 = require("@naturalcycles/js-lib");
6
+ const colors_1 = require("../../../colors");
7
+ function notNullishPredicate(item) {
8
+ return item !== undefined && item !== null;
9
+ }
10
+ exports.notNullishPredicate = notNullishPredicate;
11
+ /**
12
+ * Like pMap, but for streams.
13
+ * Inspired by `through2`.
14
+ * Main feature is concurrency control (implemented via `through2-concurrent`) and convenient options.
15
+ * Using this allows native stream .pipe() to work and use backpressure.
16
+ *
17
+ * Only works in objectMode (due to through2Concurrent).
18
+ *
19
+ * Concurrency defaults to 16.
20
+ *
21
+ * If an Array is returned by `mapper` - it will be flattened and multiple results will be emitted from it. Tested by Array.isArray().
22
+ */
23
+ function transformMap2(mapper, opt = {}) {
24
+ const { concurrency = 16, predicate = notNullishPredicate, errorMode = js_lib_1.ErrorMode.THROW_IMMEDIATELY, flattenArrayOutput, onError, beforeFinal, metric = 'stream', logger = console, } = opt;
25
+ let index = -1;
26
+ let isRejected = false;
27
+ let errors = 0;
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,
36
+ 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
42
+ if (collectedErrors.length) {
43
+ // emit Aggregated error
44
+ cb(new js_lib_1.AggregatedError(collectedErrors));
45
+ }
46
+ else {
47
+ // emit no error
48
+ cb();
49
+ }
50
+ },
51
+ async transform(chunk, _encoding, cb) {
52
+ index++;
53
+ // console.log('transform', {index})
54
+ // Stop processing if THROW_IMMEDIATELY mode is used
55
+ if (isRejected && errorMode === js_lib_1.ErrorMode.THROW_IMMEDIATELY)
56
+ return cb();
57
+ let calledBack = false;
58
+ // Allow up to 1 x concurrency "buffer" (aka highWatermark)
59
+ if (q.queueSize < concurrency) {
60
+ cb();
61
+ calledBack = true;
62
+ }
63
+ await q.push(async () => {
64
+ try {
65
+ 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)
66
+ const res = await mapper(chunk, currentIndex);
67
+ const passedResults = await (0, js_lib_1.pFilter)(flattenArrayOutput && Array.isArray(res) ? res : [res], async (r) => await predicate(r, currentIndex));
68
+ passedResults.forEach(r => this.push(r));
69
+ }
70
+ catch (err) {
71
+ logger.error(err);
72
+ errors++;
73
+ logErrorStats(logger);
74
+ if (onError) {
75
+ try {
76
+ onError(err, chunk);
77
+ }
78
+ catch { }
79
+ }
80
+ if (errorMode === js_lib_1.ErrorMode.THROW_IMMEDIATELY) {
81
+ isRejected = true;
82
+ // Emit error immediately
83
+ // return cb(err as Error)
84
+ return this.emit('error', err);
85
+ }
86
+ if (errorMode === js_lib_1.ErrorMode.THROW_AGGREGATED) {
87
+ collectedErrors.push(err);
88
+ }
89
+ }
90
+ });
91
+ // Resolved, which means it STARTED processing
92
+ // This means we can take more load
93
+ if (!calledBack) {
94
+ cb();
95
+ }
96
+ },
97
+ });
98
+ function logErrorStats(logger, final = false) {
99
+ if (!errors)
100
+ return;
101
+ logger.log(`${metric} ${final ? 'final ' : ''}errors: ${(0, colors_1.yellow)(errors)}`);
102
+ }
103
+ }
104
+ exports.transformMap2 = transformMap2;
@@ -1,4 +1,4 @@
1
- import { AsyncMapper, AsyncPredicate, ErrorMode } from '@naturalcycles/js-lib';
1
+ import { AsyncMapper, 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
  /**
@@ -41,6 +41,7 @@ export interface TransformMapOptions<IN = any, OUT = IN> {
41
41
  * If defined - called BEFORE `final()` callback is called.
42
42
  */
43
43
  beforeFinal?: () => any;
44
+ logger?: CommonLogger;
44
45
  }
45
46
  export declare function notNullishPredicate(item: any): boolean;
46
47
  /**
@@ -21,7 +21,7 @@ exports.notNullishPredicate = notNullishPredicate;
21
21
  * If an Array is returned by `mapper` - it will be flattened and multiple results will be emitted from it. Tested by Array.isArray().
22
22
  */
23
23
  function transformMap(mapper, opt = {}) {
24
- const { concurrency = 16, predicate = notNullishPredicate, errorMode = js_lib_1.ErrorMode.THROW_IMMEDIATELY, flattenArrayOutput, onError, beforeFinal, metric = 'stream', } = opt;
24
+ const { concurrency = 16, predicate = notNullishPredicate, errorMode = js_lib_1.ErrorMode.THROW_IMMEDIATELY, flattenArrayOutput, onError, beforeFinal, metric = 'stream', logger = console, } = opt;
25
25
  let index = -1;
26
26
  let isRejected = false;
27
27
  let errors = 0;
@@ -31,7 +31,7 @@ function transformMap(mapper, opt = {}) {
31
31
  // autoDestroy: true,
32
32
  async final(cb) {
33
33
  // console.log('transformMap final')
34
- logErrorStats(true);
34
+ logErrorStats(logger, true);
35
35
  await beforeFinal?.(); // call beforeFinal if defined
36
36
  if (collectedErrors.length) {
37
37
  // emit Aggregated error
@@ -64,9 +64,9 @@ function transformMap(mapper, opt = {}) {
64
64
  }
65
65
  }
66
66
  catch (err) {
67
- console.error(err);
67
+ logger.error(err);
68
68
  errors++;
69
- logErrorStats();
69
+ logErrorStats(logger);
70
70
  if (onError) {
71
71
  try {
72
72
  onError(err, chunk);
@@ -85,10 +85,10 @@ function transformMap(mapper, opt = {}) {
85
85
  cb();
86
86
  }
87
87
  });
88
- function logErrorStats(final = false) {
88
+ function logErrorStats(logger, final = false) {
89
89
  if (!errors)
90
90
  return;
91
- console.log(`${metric} ${final ? 'final ' : ''}errors: ${(0, colors_1.yellow)(errors)}`);
91
+ logger.log(`${metric} ${final ? 'final ' : ''}errors: ${(0, colors_1.yellow)(errors)}`);
92
92
  }
93
93
  }
94
94
  exports.transformMap = transformMap;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/nodejs-lib",
3
- "version": "12.54.0",
3
+ "version": "12.55.0",
4
4
  "scripts": {
5
5
  "prepare": "husky install",
6
6
  "docs-serve": "vuepress dev docs",
@@ -0,0 +1,142 @@
1
+ import { Transform } from 'stream'
2
+ import {
3
+ AggregatedError,
4
+ AsyncMapper,
5
+ CommonLogger,
6
+ ErrorMode,
7
+ pFilter,
8
+ PQueue,
9
+ } from '@naturalcycles/js-lib'
10
+ import { yellow } from '../../../colors'
11
+ import { TransformTyped } from '../../stream.model'
12
+ import { TransformMapOptions } from '../transformMap'
13
+
14
+ export function notNullishPredicate(item: any): boolean {
15
+ return item !== undefined && item !== null
16
+ }
17
+
18
+ /**
19
+ * Like pMap, but for streams.
20
+ * Inspired by `through2`.
21
+ * Main feature is concurrency control (implemented via `through2-concurrent`) and convenient options.
22
+ * Using this allows native stream .pipe() to work and use backpressure.
23
+ *
24
+ * Only works in objectMode (due to through2Concurrent).
25
+ *
26
+ * Concurrency defaults to 16.
27
+ *
28
+ * If an Array is returned by `mapper` - it will be flattened and multiple results will be emitted from it. Tested by Array.isArray().
29
+ */
30
+ export function transformMap2<IN = any, OUT = IN>(
31
+ mapper: AsyncMapper<IN, OUT>,
32
+ opt: TransformMapOptions<IN, OUT> = {},
33
+ ): TransformTyped<IN, OUT> {
34
+ const {
35
+ concurrency = 16,
36
+ predicate = notNullishPredicate,
37
+ errorMode = ErrorMode.THROW_IMMEDIATELY,
38
+ flattenArrayOutput,
39
+ onError,
40
+ beforeFinal,
41
+ metric = 'stream',
42
+ logger = console,
43
+ } = opt
44
+
45
+ let index = -1
46
+ let isRejected = false
47
+ let errors = 0
48
+ const collectedErrors: Error[] = [] // only used if errorMode == THROW_AGGREGATED
49
+
50
+ const q = new PQueue({
51
+ concurrency,
52
+ resolveOn: 'start',
53
+ // debug: true,
54
+ })
55
+
56
+ return new Transform({
57
+ objectMode: true,
58
+
59
+ async final(cb) {
60
+ // console.log('transformMap final', {index}, q.inFlight, q.queueSize)
61
+
62
+ // wait for the current inFlight jobs to complete and push their results
63
+ await q.onIdle()
64
+
65
+ logErrorStats(logger, true)
66
+
67
+ await beforeFinal?.() // call beforeFinal if defined
68
+
69
+ if (collectedErrors.length) {
70
+ // emit Aggregated error
71
+ cb(new AggregatedError(collectedErrors))
72
+ } else {
73
+ // emit no error
74
+ cb()
75
+ }
76
+ },
77
+
78
+ async transform(this: Transform, chunk: IN, _encoding, cb) {
79
+ index++
80
+ // console.log('transform', {index})
81
+
82
+ // Stop processing if THROW_IMMEDIATELY mode is used
83
+ if (isRejected && errorMode === ErrorMode.THROW_IMMEDIATELY) return cb()
84
+
85
+ let calledBack = false
86
+
87
+ // Allow up to 1 x concurrency "buffer" (aka highWatermark)
88
+ if (q.queueSize < concurrency) {
89
+ cb()
90
+ calledBack = true
91
+ }
92
+
93
+ await q.push(async () => {
94
+ try {
95
+ 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)
96
+ const res = await mapper(chunk, currentIndex)
97
+ const passedResults = await pFilter(
98
+ flattenArrayOutput && Array.isArray(res) ? res : [res],
99
+ async r => await predicate(r, currentIndex),
100
+ )
101
+
102
+ passedResults.forEach(r => this.push(r))
103
+ } catch (err) {
104
+ logger.error(err)
105
+
106
+ errors++
107
+
108
+ logErrorStats(logger)
109
+
110
+ if (onError) {
111
+ try {
112
+ onError(err, chunk)
113
+ } catch {}
114
+ }
115
+
116
+ if (errorMode === ErrorMode.THROW_IMMEDIATELY) {
117
+ isRejected = true
118
+ // Emit error immediately
119
+ // return cb(err as Error)
120
+ return this.emit('error', err as Error)
121
+ }
122
+
123
+ if (errorMode === ErrorMode.THROW_AGGREGATED) {
124
+ collectedErrors.push(err as Error)
125
+ }
126
+ }
127
+ })
128
+
129
+ // Resolved, which means it STARTED processing
130
+ // This means we can take more load
131
+ if (!calledBack) {
132
+ cb()
133
+ }
134
+ },
135
+ })
136
+
137
+ function logErrorStats(logger: CommonLogger, final = false): void {
138
+ if (!errors) return
139
+
140
+ logger.log(`${metric} ${final ? 'final ' : ''}errors: ${yellow(errors)}`)
141
+ }
142
+ }
@@ -3,6 +3,7 @@ import {
3
3
  AggregatedError,
4
4
  AsyncMapper,
5
5
  AsyncPredicate,
6
+ CommonLogger,
6
7
  ErrorMode,
7
8
  pFilter,
8
9
  } from '@naturalcycles/js-lib'
@@ -57,6 +58,8 @@ export interface TransformMapOptions<IN = any, OUT = IN> {
57
58
  * If defined - called BEFORE `final()` callback is called.
58
59
  */
59
60
  beforeFinal?: () => any
61
+
62
+ logger?: CommonLogger
60
63
  }
61
64
 
62
65
  export function notNullishPredicate(item: any): boolean {
@@ -87,6 +90,7 @@ export function transformMap<IN = any, OUT = IN>(
87
90
  onError,
88
91
  beforeFinal,
89
92
  metric = 'stream',
93
+ logger = console,
90
94
  } = opt
91
95
 
92
96
  let index = -1
@@ -101,7 +105,7 @@ export function transformMap<IN = any, OUT = IN>(
101
105
  async final(cb) {
102
106
  // console.log('transformMap final')
103
107
 
104
- logErrorStats(true)
108
+ logErrorStats(logger, true)
105
109
 
106
110
  await beforeFinal?.() // call beforeFinal if defined
107
111
 
@@ -144,11 +148,11 @@ export function transformMap<IN = any, OUT = IN>(
144
148
  cb() // done processing
145
149
  }
146
150
  } catch (err) {
147
- console.error(err)
151
+ logger.error(err)
148
152
 
149
153
  errors++
150
154
 
151
- logErrorStats()
155
+ logErrorStats(logger)
152
156
 
153
157
  if (onError) {
154
158
  try {
@@ -172,9 +176,9 @@ export function transformMap<IN = any, OUT = IN>(
172
176
  },
173
177
  )
174
178
 
175
- function logErrorStats(final = false): void {
179
+ function logErrorStats(logger: CommonLogger, final = false): void {
176
180
  if (!errors) return
177
181
 
178
- console.log(`${metric} ${final ? 'final ' : ''}errors: ${yellow(errors)}`)
182
+ logger.log(`${metric} ${final ? 'final ' : ''}errors: ${yellow(errors)}`)
179
183
  }
180
184
  }