@naturalcycles/nodejs-lib 13.15.0 → 13.17.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.
@@ -30,7 +30,7 @@ export declare function appendToGithubOutput(obj: AnyObject, prefix?: string): v
30
30
  /**
31
31
  * https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary
32
32
  */
33
- export declare function appendToGithubSummary(str: string): void;
33
+ export declare function appendToGithubSummary(...lines: string[]): void;
34
34
  /**
35
35
  * Turns Object with keys/values into a *.sh script that exports all keys as values.
36
36
  *
@@ -81,10 +81,11 @@ exports.appendToGithubOutput = appendToGithubOutput;
81
81
  /**
82
82
  * https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary
83
83
  */
84
- function appendToGithubSummary(str) {
84
+ function appendToGithubSummary(...lines) {
85
85
  const { GITHUB_STEP_SUMMARY } = process.env;
86
86
  if (GITHUB_STEP_SUMMARY) {
87
- node_fs_1.default.appendFileSync(GITHUB_STEP_SUMMARY, str + '\n');
87
+ const str = lines.join('\n') + '\n';
88
+ node_fs_1.default.appendFileSync(GITHUB_STEP_SUMMARY, str);
88
89
  console.log(`GITHUB_STEP_SUMMARY appended:\n${str}`);
89
90
  }
90
91
  }
@@ -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
  }
@@ -1,4 +1,4 @@
1
- import { AbortableAsyncMapper, AsyncPredicate, CommonLogger, END, ErrorMode, SKIP } from '@naturalcycles/js-lib';
1
+ import { AbortableAsyncMapper, AsyncPredicate, CommonLogger, END, ErrorMode, SKIP, StringMap, UnixTimestampMillisNumber } from '@naturalcycles/js-lib';
2
2
  import { TransformTyped } from '../stream.model';
3
3
  export interface TransformMapOptions<IN = any, OUT = IN> {
4
4
  /**
@@ -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,20 @@ 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
+ started: UnixTimestampMillisNumber;
65
+ }
41
66
  /**
42
67
  * Like pMap, but for streams.
43
68
  * Inspired by `through2`.
@@ -51,3 +76,7 @@ export interface TransformMapOptions<IN = any, OUT = IN> {
51
76
  * If an Array is returned by `mapper` - it will be flattened and multiple results will be emitted from it. Tested by Array.isArray().
52
77
  */
53
78
  export declare function transformMap<IN = any, OUT = IN>(mapper: AbortableAsyncMapper<IN, OUT | typeof SKIP | typeof END>, opt?: TransformMapOptions<IN, OUT>): TransformTyped<IN, OUT>;
79
+ export declare function appendTransformMapStatsToGithubSummary(stats: TransformMapStats & {
80
+ name?: string;
81
+ extra?: StringMap<any>;
82
+ }): void;
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.transformMap = void 0;
3
+ exports.appendTransformMapStatsToGithubSummary = exports.transformMap = void 0;
4
4
  const js_lib_1 = require("@naturalcycles/js-lib");
5
5
  const through2Concurrent = require("through2-concurrent");
6
6
  const colors_1 = require("../../colors/colors");
7
+ const json2env_1 = require("../../fs/json2env");
7
8
  const stream_util_1 = require("../stream.util");
8
9
  // doesn't work, cause here we don't construct our Transform instance ourselves
9
10
  // export class TransformMap extends AbortableTransform {}
@@ -21,8 +22,10 @@ const stream_util_1 = require("../stream.util");
21
22
  */
22
23
  function transformMap(mapper, opt = {}) {
23
24
  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
+ errorMode = js_lib_1.ErrorMode.THROW_IMMEDIATELY, flattenArrayOutput, onError, onDone, metric = 'stream', logger = console, } = opt;
26
+ const started = Date.now();
25
27
  let index = -1;
28
+ let countOut = 0;
26
29
  let isSettled = false;
27
30
  let errors = 0;
28
31
  const collectedErrors = []; // only used if errorMode == THROW_AGGREGATED
@@ -32,11 +35,27 @@ function transformMap(mapper, opt = {}) {
32
35
  // console.log('transformMap final')
33
36
  logErrorStats(true);
34
37
  if (collectedErrors.length) {
38
+ onDone?.({
39
+ ok: false,
40
+ collectedErrors,
41
+ countErrors: errors,
42
+ countIn: index + 1,
43
+ countOut,
44
+ started,
45
+ });
35
46
  // emit Aggregated error
36
47
  cb(new AggregateError(collectedErrors, `transformMap resulted in ${collectedErrors.length} error(s)`));
37
48
  }
38
49
  else {
39
50
  // emit no error
51
+ onDone?.({
52
+ ok: true,
53
+ collectedErrors,
54
+ countErrors: errors,
55
+ countIn: index + 1,
56
+ countOut,
57
+ started,
58
+ });
40
59
  cb();
41
60
  }
42
61
  },
@@ -54,6 +73,7 @@ function transformMap(mapper, opt = {}) {
54
73
  }
55
74
  return r !== js_lib_1.SKIP && (!predicate || (await predicate(r, currentIndex)));
56
75
  });
76
+ countOut += passedResults.length;
57
77
  passedResults.forEach(r => this.push(r));
58
78
  if (isSettled) {
59
79
  logger.log(`transformMap END received at index ${currentIndex}`);
@@ -73,6 +93,14 @@ function transformMap(mapper, opt = {}) {
73
93
  }
74
94
  if (errorMode === js_lib_1.ErrorMode.THROW_IMMEDIATELY) {
75
95
  isSettled = true;
96
+ onDone?.({
97
+ ok: false,
98
+ collectedErrors,
99
+ countErrors: errors,
100
+ countIn: index + 1,
101
+ countOut,
102
+ started,
103
+ });
76
104
  return cb(err); // Emit error immediately
77
105
  }
78
106
  if (errorMode === js_lib_1.ErrorMode.THROW_AGGREGATED) {
@@ -89,3 +117,14 @@ function transformMap(mapper, opt = {}) {
89
117
  }
90
118
  }
91
119
  exports.transformMap = transformMap;
120
+ function appendTransformMapStatsToGithubSummary(stats) {
121
+ const { countIn, countOut, countErrors, started, name = 'Transform', extra = {} } = stats;
122
+ (0, json2env_1.appendToGithubSummary)(...[
123
+ `### ${name} summary\n`,
124
+ `${(0, js_lib_1._since)(started)} spent`,
125
+ `${(0, js_lib_1._hc)(countIn)} / ${(0, js_lib_1._hc)(countOut)} rows in / out`,
126
+ countErrors ? `${countErrors} errors` : '',
127
+ ...Object.entries(extra).map(([k, v]) => `${k}: ${v}`),
128
+ ].filter(Boolean));
129
+ }
130
+ exports.appendTransformMapStatsToGithubSummary = appendTransformMapStatsToGithubSummary;
@@ -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,11 @@ 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
+ const started = Date.now();
19
+ let index = -1;
20
+ let countOut = 0;
19
21
  let isSettled = false;
20
22
  let errors = 0;
21
23
  const collectedErrors = []; // only used if errorMode == THROW_AGGREGATED
@@ -37,6 +39,7 @@ function transformMapSync(mapper, opt = {}) {
37
39
  }
38
40
  return r !== js_lib_1.SKIP && (!predicate || predicate(r, currentIndex));
39
41
  });
42
+ countOut += passedResults.length;
40
43
  passedResults.forEach(r => this.push(r));
41
44
  if (isSettled) {
42
45
  logger.log(`transformMapSync END received at index ${currentIndex}`);
@@ -56,6 +59,14 @@ function transformMapSync(mapper, opt = {}) {
56
59
  }
57
60
  if (errorMode === js_lib_1.ErrorMode.THROW_IMMEDIATELY) {
58
61
  isSettled = true;
62
+ onDone?.({
63
+ ok: false,
64
+ collectedErrors,
65
+ countErrors: errors,
66
+ countIn: index + 1,
67
+ countOut,
68
+ started,
69
+ });
59
70
  // Emit error immediately
60
71
  return cb(err);
61
72
  }
@@ -69,11 +80,27 @@ function transformMapSync(mapper, opt = {}) {
69
80
  // console.log('transformMap final')
70
81
  logErrorStats(true);
71
82
  if (collectedErrors.length) {
83
+ onDone?.({
84
+ ok: false,
85
+ collectedErrors,
86
+ countErrors: errors,
87
+ countIn: index + 1,
88
+ countOut,
89
+ started,
90
+ });
72
91
  // emit Aggregated error
73
92
  cb(new AggregateError(collectedErrors, `transformMapSync resulted in ${collectedErrors.length} error(s)`));
74
93
  }
75
94
  else {
76
95
  // emit no error
96
+ onDone?.({
97
+ ok: true,
98
+ collectedErrors,
99
+ countErrors: errors,
100
+ countIn: index + 1,
101
+ countOut,
102
+ started,
103
+ });
77
104
  cb();
78
105
  }
79
106
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/nodejs-lib",
3
- "version": "13.15.0",
3
+ "version": "13.17.0",
4
4
  "scripts": {
5
5
  "prepare": "husky",
6
6
  "docs-serve": "vuepress dev docs",
@@ -120,10 +120,11 @@ export function appendToGithubOutput(obj: AnyObject, prefix = ''): void {
120
120
  /**
121
121
  * https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary
122
122
  */
123
- export function appendToGithubSummary(str: string): void {
123
+ export function appendToGithubSummary(...lines: string[]): void {
124
124
  const { GITHUB_STEP_SUMMARY } = process.env
125
125
  if (GITHUB_STEP_SUMMARY) {
126
- fs.appendFileSync(GITHUB_STEP_SUMMARY, str + '\n')
126
+ const str = lines.join('\n') + '\n'
127
+ fs.appendFileSync(GITHUB_STEP_SUMMARY, str)
127
128
  console.log(`GITHUB_STEP_SUMMARY appended:\n${str}`)
128
129
  }
129
130
  }
@@ -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
  }
@@ -1,5 +1,7 @@
1
1
  import {
2
2
  _anyToError,
3
+ _hc,
4
+ _since,
3
5
  AbortableAsyncMapper,
4
6
  AsyncPredicate,
5
7
  CommonLogger,
@@ -7,9 +9,12 @@ import {
7
9
  ErrorMode,
8
10
  pFilter,
9
11
  SKIP,
12
+ StringMap,
13
+ UnixTimestampMillisNumber,
10
14
  } from '@naturalcycles/js-lib'
11
15
  import through2Concurrent = require('through2-concurrent')
12
16
  import { yellow } from '../../colors/colors'
17
+ import { appendToGithubSummary } from '../../fs/json2env'
13
18
  import { AbortableTransform } from '../pipeline/pipeline'
14
19
  import { TransformTyped } from '../stream.model'
15
20
  import { pipelineClose } from '../stream.util'
@@ -49,6 +54,18 @@ export interface TransformMapOptions<IN = any, OUT = IN> {
49
54
  */
50
55
  onError?: (err: Error, input: IN) => any
51
56
 
57
+ /**
58
+ * A hook that is called when the last item is finished processing.
59
+ * stats object is passed, containing countIn and countOut -
60
+ * number of items that entered the transform and number of items that left it.
61
+ *
62
+ * Callback is called **before** [possible] Aggregated error is thrown,
63
+ * and before [possible] THROW_IMMEDIATELY error.
64
+ *
65
+ * onDone callback will be called before Error is thrown.
66
+ */
67
+ onDone?: (stats: TransformMapStats) => any
68
+
52
69
  /**
53
70
  * Progress metric
54
71
  *
@@ -59,6 +76,21 @@ export interface TransformMapOptions<IN = any, OUT = IN> {
59
76
  logger?: CommonLogger
60
77
  }
61
78
 
79
+ export interface TransformMapStats {
80
+ /**
81
+ * True if transform was successful (didn't throw Immediate or Aggregated error).
82
+ */
83
+ ok: boolean
84
+ /**
85
+ * Only used (and returned) for ErrorMode.Aggregated
86
+ */
87
+ collectedErrors: Error[]
88
+ countErrors: number
89
+ countIn: number
90
+ countOut: number
91
+ started: UnixTimestampMillisNumber
92
+ }
93
+
62
94
  // doesn't work, cause here we don't construct our Transform instance ourselves
63
95
  // export class TransformMap extends AbortableTransform {}
64
96
 
@@ -84,11 +116,14 @@ export function transformMap<IN = any, OUT = IN>(
84
116
  errorMode = ErrorMode.THROW_IMMEDIATELY,
85
117
  flattenArrayOutput,
86
118
  onError,
119
+ onDone,
87
120
  metric = 'stream',
88
121
  logger = console,
89
122
  } = opt
90
123
 
124
+ const started = Date.now()
91
125
  let index = -1
126
+ let countOut = 0
92
127
  let isSettled = false
93
128
  let errors = 0
94
129
  const collectedErrors: Error[] = [] // only used if errorMode == THROW_AGGREGATED
@@ -102,6 +137,15 @@ export function transformMap<IN = any, OUT = IN>(
102
137
  logErrorStats(true)
103
138
 
104
139
  if (collectedErrors.length) {
140
+ onDone?.({
141
+ ok: false,
142
+ collectedErrors,
143
+ countErrors: errors,
144
+ countIn: index + 1,
145
+ countOut,
146
+ started,
147
+ })
148
+
105
149
  // emit Aggregated error
106
150
  cb(
107
151
  new AggregateError(
@@ -111,6 +155,16 @@ export function transformMap<IN = any, OUT = IN>(
111
155
  )
112
156
  } else {
113
157
  // emit no error
158
+
159
+ onDone?.({
160
+ ok: true,
161
+ collectedErrors,
162
+ countErrors: errors,
163
+ countIn: index + 1,
164
+ countOut,
165
+ started,
166
+ })
167
+
114
168
  cb()
115
169
  }
116
170
  },
@@ -134,6 +188,7 @@ export function transformMap<IN = any, OUT = IN>(
134
188
  },
135
189
  )
136
190
 
191
+ countOut += passedResults.length
137
192
  passedResults.forEach(r => this.push(r))
138
193
 
139
194
  if (isSettled) {
@@ -155,6 +210,14 @@ export function transformMap<IN = any, OUT = IN>(
155
210
 
156
211
  if (errorMode === ErrorMode.THROW_IMMEDIATELY) {
157
212
  isSettled = true
213
+ onDone?.({
214
+ ok: false,
215
+ collectedErrors,
216
+ countErrors: errors,
217
+ countIn: index + 1,
218
+ countOut,
219
+ started,
220
+ })
158
221
  return cb(err) // Emit error immediately
159
222
  }
160
223
 
@@ -173,3 +236,19 @@ export function transformMap<IN = any, OUT = IN>(
173
236
  logger.log(`${metric} ${final ? 'final ' : ''}errors: ${yellow(errors)}`)
174
237
  }
175
238
  }
239
+
240
+ export function appendTransformMapStatsToGithubSummary(
241
+ stats: TransformMapStats & { name?: string; extra?: StringMap<any> },
242
+ ): void {
243
+ const { countIn, countOut, countErrors, started, name = 'Transform', extra = {} } = stats
244
+
245
+ appendToGithubSummary(
246
+ ...[
247
+ `### ${name} summary\n`,
248
+ `${_since(started)} spent`,
249
+ `${_hc(countIn)} / ${_hc(countOut)} rows in / out`,
250
+ countErrors ? `${countErrors} errors` : '',
251
+ ...Object.entries(extra).map(([k, v]) => `${k}: ${v}`),
252
+ ].filter(Boolean),
253
+ )
254
+ }
@@ -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,20 @@ 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
+ const started = Date.now()
92
+ let index = -1
93
+ let countOut = 0
78
94
  let isSettled = false
79
95
  let errors = 0
80
96
  const collectedErrors: Error[] = [] // only used if errorMode == THROW_AGGREGATED
@@ -100,6 +116,7 @@ export function transformMapSync<IN = any, OUT = IN>(
100
116
  return r !== SKIP && (!predicate || predicate(r, currentIndex))
101
117
  })
102
118
 
119
+ countOut += passedResults.length
103
120
  passedResults.forEach(r => this.push(r))
104
121
 
105
122
  if (isSettled) {
@@ -122,6 +139,14 @@ export function transformMapSync<IN = any, OUT = IN>(
122
139
 
123
140
  if (errorMode === ErrorMode.THROW_IMMEDIATELY) {
124
141
  isSettled = true
142
+ onDone?.({
143
+ ok: false,
144
+ collectedErrors,
145
+ countErrors: errors,
146
+ countIn: index + 1,
147
+ countOut,
148
+ started,
149
+ })
125
150
  // Emit error immediately
126
151
  return cb(err as Error)
127
152
  }
@@ -139,6 +164,15 @@ export function transformMapSync<IN = any, OUT = IN>(
139
164
  logErrorStats(true)
140
165
 
141
166
  if (collectedErrors.length) {
167
+ onDone?.({
168
+ ok: false,
169
+ collectedErrors,
170
+ countErrors: errors,
171
+ countIn: index + 1,
172
+ countOut,
173
+ started,
174
+ })
175
+
142
176
  // emit Aggregated error
143
177
  cb(
144
178
  new AggregateError(
@@ -148,6 +182,16 @@ export function transformMapSync<IN = any, OUT = IN>(
148
182
  )
149
183
  } else {
150
184
  // emit no error
185
+
186
+ onDone?.({
187
+ ok: true,
188
+ collectedErrors,
189
+ countErrors: errors,
190
+ countIn: index + 1,
191
+ countOut,
192
+ started,
193
+ })
194
+
151
195
  cb()
152
196
  }
153
197
  },