@naturalcycles/nodejs-lib 13.15.0 → 13.16.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.
@@ -76,6 +76,11 @@ export interface ProgressLoggerCfg<T = any> {
76
76
  * chunk is undefined for "final" stats, otherwise is defined.
77
77
  */
78
78
  extra?: (chunk: T | undefined, index: number) => AnyObject;
79
+ /**
80
+ * Hook that is called when the last item is passed through.
81
+ * Passes the final stats as `ProgressLogItem`.
82
+ */
83
+ onProgressDone?: (stats: ProgressLogItem) => any;
79
84
  /**
80
85
  * If specified - will multiply the counter by this number.
81
86
  * Useful e.g when using `transformChunk({ chunkSize: 500 })`, so
@@ -107,6 +107,7 @@ class ProgressLogger {
107
107
  }
108
108
  else if (final) {
109
109
  logger.log(`${(0, colors_1.boldWhite)(metric)} took ${(0, colors_1.yellow)((0, js_lib_1._since)(this.started))} to process ${(0, colors_1.yellow)(batchedProgress)} rows with total RPS of ${(0, colors_1.yellow)(rpsTotal)}`);
110
+ this.cfg.onProgressDone?.(o);
110
111
  }
111
112
  }
112
113
  }
@@ -30,6 +30,17 @@ export interface TransformMapOptions<IN = any, OUT = IN> {
30
30
  * Called BEFORE observable will emit error (unless skipErrors is set to true).
31
31
  */
32
32
  onError?: (err: Error, input: IN) => any;
33
+ /**
34
+ * A hook that is called when the last item is finished processing.
35
+ * stats object is passed, containing countIn and countOut -
36
+ * number of items that entered the transform and number of items that left it.
37
+ *
38
+ * Callback is called **before** [possible] Aggregated error is thrown,
39
+ * and before [possible] THROW_IMMEDIATELY error.
40
+ *
41
+ * onDone callback will be called before Error is thrown.
42
+ */
43
+ onDone?: (stats: TransformMapStats) => any;
33
44
  /**
34
45
  * Progress metric
35
46
  *
@@ -38,6 +49,19 @@ export interface TransformMapOptions<IN = any, OUT = IN> {
38
49
  metric?: string;
39
50
  logger?: CommonLogger;
40
51
  }
52
+ export interface TransformMapStats {
53
+ /**
54
+ * True if transform was successful (didn't throw Immediate or Aggregated error).
55
+ */
56
+ ok: boolean;
57
+ /**
58
+ * Only used (and returned) for ErrorMode.Aggregated
59
+ */
60
+ collectedErrors: Error[];
61
+ countErrors: number;
62
+ countIn: number;
63
+ countOut: number;
64
+ }
41
65
  /**
42
66
  * Like pMap, but for streams.
43
67
  * Inspired by `through2`.
@@ -21,8 +21,9 @@ const stream_util_1 = require("../stream.util");
21
21
  */
22
22
  function transformMap(mapper, opt = {}) {
23
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;
24
+ errorMode = js_lib_1.ErrorMode.THROW_IMMEDIATELY, flattenArrayOutput, onError, onDone, metric = 'stream', logger = console, } = opt;
25
25
  let index = -1;
26
+ let countOut = 0;
26
27
  let isSettled = false;
27
28
  let errors = 0;
28
29
  const collectedErrors = []; // only used if errorMode == THROW_AGGREGATED
@@ -32,11 +33,25 @@ function transformMap(mapper, opt = {}) {
32
33
  // console.log('transformMap final')
33
34
  logErrorStats(true);
34
35
  if (collectedErrors.length) {
36
+ onDone?.({
37
+ ok: false,
38
+ collectedErrors,
39
+ countErrors: errors,
40
+ countIn: index + 1,
41
+ countOut,
42
+ });
35
43
  // emit Aggregated error
36
44
  cb(new AggregateError(collectedErrors, `transformMap resulted in ${collectedErrors.length} error(s)`));
37
45
  }
38
46
  else {
39
47
  // emit no error
48
+ onDone?.({
49
+ ok: true,
50
+ collectedErrors,
51
+ countErrors: errors,
52
+ countIn: index + 1,
53
+ countOut,
54
+ });
40
55
  cb();
41
56
  }
42
57
  },
@@ -54,6 +69,7 @@ function transformMap(mapper, opt = {}) {
54
69
  }
55
70
  return r !== js_lib_1.SKIP && (!predicate || (await predicate(r, currentIndex)));
56
71
  });
72
+ countOut += passedResults.length;
57
73
  passedResults.forEach(r => this.push(r));
58
74
  if (isSettled) {
59
75
  logger.log(`transformMap END received at index ${currentIndex}`);
@@ -73,6 +89,13 @@ function transformMap(mapper, opt = {}) {
73
89
  }
74
90
  if (errorMode === js_lib_1.ErrorMode.THROW_IMMEDIATELY) {
75
91
  isSettled = true;
92
+ onDone?.({
93
+ ok: false,
94
+ collectedErrors,
95
+ countErrors: errors,
96
+ countIn: index + 1,
97
+ countOut,
98
+ });
76
99
  return cb(err); // Emit error immediately
77
100
  }
78
101
  if (errorMode === js_lib_1.ErrorMode.THROW_AGGREGATED) {
@@ -1,6 +1,7 @@
1
1
  import { CommonLogger, END, ErrorMode, Mapper, Predicate, SKIP } from '@naturalcycles/js-lib';
2
2
  import { AbortableTransform } from '../pipeline/pipeline';
3
3
  import { TransformTyped } from '../stream.model';
4
+ import { TransformMapStats } from './transformMap';
4
5
  export interface TransformMapSyncOptions<IN = any, OUT = IN> {
5
6
  /**
6
7
  * @default true
@@ -28,6 +29,17 @@ export interface TransformMapSyncOptions<IN = any, OUT = IN> {
28
29
  * Called BEFORE observable will emit error (unless skipErrors is set to true).
29
30
  */
30
31
  onError?: (err: Error, input: IN) => any;
32
+ /**
33
+ * A hook that is called when the last item is finished processing.
34
+ * stats object is passed, containing countIn and countOut -
35
+ * number of items that entered the transform and number of items that left it.
36
+ *
37
+ * Callback is called **before** [possible] Aggregated error is thrown,
38
+ * and before [possible] THROW_IMMEDIATELY error.
39
+ *
40
+ * onDone callback will be called before Error is thrown.
41
+ */
42
+ onDone?: (stats: TransformMapStats) => any;
31
43
  /**
32
44
  * Progress metric
33
45
  *
@@ -13,9 +13,10 @@ exports.TransformMapSync = TransformMapSync;
13
13
  * Supposedly faster, for cases when async is not needed.
14
14
  */
15
15
  function transformMapSync(mapper, opt = {}) {
16
- let index = -1;
17
16
  const { predicate, // defaults to "no predicate" (pass everything)
18
- errorMode = js_lib_1.ErrorMode.THROW_IMMEDIATELY, flattenArrayOutput = false, onError, metric = 'stream', objectMode = true, logger = console, } = opt;
17
+ errorMode = js_lib_1.ErrorMode.THROW_IMMEDIATELY, flattenArrayOutput = false, onError, onDone, metric = 'stream', objectMode = true, logger = console, } = opt;
18
+ let index = -1;
19
+ let countOut = 0;
19
20
  let isSettled = false;
20
21
  let errors = 0;
21
22
  const collectedErrors = []; // only used if errorMode == THROW_AGGREGATED
@@ -37,6 +38,7 @@ function transformMapSync(mapper, opt = {}) {
37
38
  }
38
39
  return r !== js_lib_1.SKIP && (!predicate || predicate(r, currentIndex));
39
40
  });
41
+ countOut += passedResults.length;
40
42
  passedResults.forEach(r => this.push(r));
41
43
  if (isSettled) {
42
44
  logger.log(`transformMapSync END received at index ${currentIndex}`);
@@ -56,6 +58,13 @@ function transformMapSync(mapper, opt = {}) {
56
58
  }
57
59
  if (errorMode === js_lib_1.ErrorMode.THROW_IMMEDIATELY) {
58
60
  isSettled = true;
61
+ onDone?.({
62
+ ok: false,
63
+ collectedErrors,
64
+ countErrors: errors,
65
+ countIn: index + 1,
66
+ countOut,
67
+ });
59
68
  // Emit error immediately
60
69
  return cb(err);
61
70
  }
@@ -69,11 +78,25 @@ function transformMapSync(mapper, opt = {}) {
69
78
  // console.log('transformMap final')
70
79
  logErrorStats(true);
71
80
  if (collectedErrors.length) {
81
+ onDone?.({
82
+ ok: false,
83
+ collectedErrors,
84
+ countErrors: errors,
85
+ countIn: index + 1,
86
+ countOut,
87
+ });
72
88
  // emit Aggregated error
73
89
  cb(new AggregateError(collectedErrors, `transformMapSync resulted in ${collectedErrors.length} error(s)`));
74
90
  }
75
91
  else {
76
92
  // emit no error
93
+ onDone?.({
94
+ ok: true,
95
+ collectedErrors,
96
+ countErrors: errors,
97
+ countIn: index + 1,
98
+ countOut,
99
+ });
77
100
  cb();
78
101
  }
79
102
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/nodejs-lib",
3
- "version": "13.15.0",
3
+ "version": "13.16.0",
4
4
  "scripts": {
5
5
  "prepare": "husky",
6
6
  "docs-serve": "vuepress dev docs",
@@ -101,6 +101,12 @@ export interface ProgressLoggerCfg<T = any> {
101
101
  */
102
102
  extra?: (chunk: T | undefined, index: number) => AnyObject
103
103
 
104
+ /**
105
+ * Hook that is called when the last item is passed through.
106
+ * Passes the final stats as `ProgressLogItem`.
107
+ */
108
+ onProgressDone?: (stats: ProgressLogItem) => any
109
+
104
110
  /**
105
111
  * If specified - will multiply the counter by this number.
106
112
  * Useful e.g when using `transformChunk({ chunkSize: 500 })`, so
@@ -298,6 +304,8 @@ export class ProgressLogger<T> implements Disposable {
298
304
  batchedProgress,
299
305
  )} rows with total RPS of ${yellow(rpsTotal)}`,
300
306
  )
307
+
308
+ this.cfg.onProgressDone?.(o)
301
309
  }
302
310
  }
303
311
  }
@@ -49,6 +49,18 @@ export interface TransformMapOptions<IN = any, OUT = IN> {
49
49
  */
50
50
  onError?: (err: Error, input: IN) => any
51
51
 
52
+ /**
53
+ * A hook that is called when the last item is finished processing.
54
+ * stats object is passed, containing countIn and countOut -
55
+ * number of items that entered the transform and number of items that left it.
56
+ *
57
+ * Callback is called **before** [possible] Aggregated error is thrown,
58
+ * and before [possible] THROW_IMMEDIATELY error.
59
+ *
60
+ * onDone callback will be called before Error is thrown.
61
+ */
62
+ onDone?: (stats: TransformMapStats) => any
63
+
52
64
  /**
53
65
  * Progress metric
54
66
  *
@@ -59,6 +71,20 @@ export interface TransformMapOptions<IN = any, OUT = IN> {
59
71
  logger?: CommonLogger
60
72
  }
61
73
 
74
+ export interface TransformMapStats {
75
+ /**
76
+ * True if transform was successful (didn't throw Immediate or Aggregated error).
77
+ */
78
+ ok: boolean
79
+ /**
80
+ * Only used (and returned) for ErrorMode.Aggregated
81
+ */
82
+ collectedErrors: Error[]
83
+ countErrors: number
84
+ countIn: number
85
+ countOut: number
86
+ }
87
+
62
88
  // doesn't work, cause here we don't construct our Transform instance ourselves
63
89
  // export class TransformMap extends AbortableTransform {}
64
90
 
@@ -84,11 +110,13 @@ export function transformMap<IN = any, OUT = IN>(
84
110
  errorMode = ErrorMode.THROW_IMMEDIATELY,
85
111
  flattenArrayOutput,
86
112
  onError,
113
+ onDone,
87
114
  metric = 'stream',
88
115
  logger = console,
89
116
  } = opt
90
117
 
91
118
  let index = -1
119
+ let countOut = 0
92
120
  let isSettled = false
93
121
  let errors = 0
94
122
  const collectedErrors: Error[] = [] // only used if errorMode == THROW_AGGREGATED
@@ -102,6 +130,14 @@ export function transformMap<IN = any, OUT = IN>(
102
130
  logErrorStats(true)
103
131
 
104
132
  if (collectedErrors.length) {
133
+ onDone?.({
134
+ ok: false,
135
+ collectedErrors,
136
+ countErrors: errors,
137
+ countIn: index + 1,
138
+ countOut,
139
+ })
140
+
105
141
  // emit Aggregated error
106
142
  cb(
107
143
  new AggregateError(
@@ -111,6 +147,15 @@ export function transformMap<IN = any, OUT = IN>(
111
147
  )
112
148
  } else {
113
149
  // emit no error
150
+
151
+ onDone?.({
152
+ ok: true,
153
+ collectedErrors,
154
+ countErrors: errors,
155
+ countIn: index + 1,
156
+ countOut,
157
+ })
158
+
114
159
  cb()
115
160
  }
116
161
  },
@@ -134,6 +179,7 @@ export function transformMap<IN = any, OUT = IN>(
134
179
  },
135
180
  )
136
181
 
182
+ countOut += passedResults.length
137
183
  passedResults.forEach(r => this.push(r))
138
184
 
139
185
  if (isSettled) {
@@ -155,6 +201,13 @@ export function transformMap<IN = any, OUT = IN>(
155
201
 
156
202
  if (errorMode === ErrorMode.THROW_IMMEDIATELY) {
157
203
  isSettled = true
204
+ onDone?.({
205
+ ok: false,
206
+ collectedErrors,
207
+ countErrors: errors,
208
+ countIn: index + 1,
209
+ countOut,
210
+ })
158
211
  return cb(err) // Emit error immediately
159
212
  }
160
213
 
@@ -11,6 +11,7 @@ import { yellow } from '../../colors/colors'
11
11
  import { AbortableTransform } from '../pipeline/pipeline'
12
12
  import { TransformTyped } from '../stream.model'
13
13
  import { pipelineClose } from '../stream.util'
14
+ import { TransformMapStats } from './transformMap'
14
15
 
15
16
  export interface TransformMapSyncOptions<IN = any, OUT = IN> {
16
17
  /**
@@ -44,6 +45,18 @@ export interface TransformMapSyncOptions<IN = any, OUT = IN> {
44
45
  */
45
46
  onError?: (err: Error, input: IN) => any
46
47
 
48
+ /**
49
+ * A hook that is called when the last item is finished processing.
50
+ * stats object is passed, containing countIn and countOut -
51
+ * number of items that entered the transform and number of items that left it.
52
+ *
53
+ * Callback is called **before** [possible] Aggregated error is thrown,
54
+ * and before [possible] THROW_IMMEDIATELY error.
55
+ *
56
+ * onDone callback will be called before Error is thrown.
57
+ */
58
+ onDone?: (stats: TransformMapStats) => any
59
+
47
60
  /**
48
61
  * Progress metric
49
62
  *
@@ -64,17 +77,19 @@ export function transformMapSync<IN = any, OUT = IN>(
64
77
  mapper: Mapper<IN, OUT | typeof SKIP | typeof END>,
65
78
  opt: TransformMapSyncOptions = {},
66
79
  ): TransformTyped<IN, OUT> {
67
- let index = -1
68
-
69
80
  const {
70
81
  predicate, // defaults to "no predicate" (pass everything)
71
82
  errorMode = ErrorMode.THROW_IMMEDIATELY,
72
83
  flattenArrayOutput = false,
73
84
  onError,
85
+ onDone,
74
86
  metric = 'stream',
75
87
  objectMode = true,
76
88
  logger = console,
77
89
  } = opt
90
+
91
+ let index = -1
92
+ let countOut = 0
78
93
  let isSettled = false
79
94
  let errors = 0
80
95
  const collectedErrors: Error[] = [] // only used if errorMode == THROW_AGGREGATED
@@ -100,6 +115,7 @@ export function transformMapSync<IN = any, OUT = IN>(
100
115
  return r !== SKIP && (!predicate || predicate(r, currentIndex))
101
116
  })
102
117
 
118
+ countOut += passedResults.length
103
119
  passedResults.forEach(r => this.push(r))
104
120
 
105
121
  if (isSettled) {
@@ -122,6 +138,13 @@ export function transformMapSync<IN = any, OUT = IN>(
122
138
 
123
139
  if (errorMode === ErrorMode.THROW_IMMEDIATELY) {
124
140
  isSettled = true
141
+ onDone?.({
142
+ ok: false,
143
+ collectedErrors,
144
+ countErrors: errors,
145
+ countIn: index + 1,
146
+ countOut,
147
+ })
125
148
  // Emit error immediately
126
149
  return cb(err as Error)
127
150
  }
@@ -139,6 +162,14 @@ export function transformMapSync<IN = any, OUT = IN>(
139
162
  logErrorStats(true)
140
163
 
141
164
  if (collectedErrors.length) {
165
+ onDone?.({
166
+ ok: false,
167
+ collectedErrors,
168
+ countErrors: errors,
169
+ countIn: index + 1,
170
+ countOut,
171
+ })
172
+
142
173
  // emit Aggregated error
143
174
  cb(
144
175
  new AggregateError(
@@ -148,6 +179,15 @@ export function transformMapSync<IN = any, OUT = IN>(
148
179
  )
149
180
  } else {
150
181
  // emit no error
182
+
183
+ onDone?.({
184
+ ok: true,
185
+ collectedErrors,
186
+ countErrors: errors,
187
+ countIn: index + 1,
188
+ countOut,
189
+ })
190
+
151
191
  cb()
152
192
  }
153
193
  },