@naturalcycles/nodejs-lib 12.56.0 → 12.59.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 (61) 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/pipeline/pipeline.d.ts +25 -3
  9. package/dist/stream/pipeline/pipeline.js +76 -9
  10. package/dist/stream/readable/readableCreate.d.ts +8 -0
  11. package/dist/stream/readable/readableCreate.js +9 -1
  12. package/dist/stream/readable/readableForEach.d.ts +2 -2
  13. package/dist/stream/readable/readableFromArray.d.ts +2 -2
  14. package/dist/stream/readable/readableFromArray.js +17 -13
  15. package/dist/stream/readable/readableMap.d.ts +2 -2
  16. package/dist/stream/readable/readableMap.js +22 -17
  17. package/dist/stream/stream.util.d.ts +4 -0
  18. package/dist/stream/stream.util.js +24 -0
  19. package/dist/stream/transform/transformLimit.d.ts +36 -1
  20. package/dist/stream/transform/transformLimit.js +33 -15
  21. package/dist/stream/transform/transformLogProgress.d.ts +2 -1
  22. package/dist/stream/transform/transformLogProgress.js +4 -4
  23. package/dist/stream/transform/transformMap.d.ts +2 -6
  24. package/dist/stream/transform/transformMap.js +51 -53
  25. package/dist/stream/transform/transformMapSimple.d.ts +2 -1
  26. package/dist/stream/transform/transformMapSimple.js +2 -2
  27. package/dist/stream/transform/transformMapSync.d.ts +2 -1
  28. package/dist/stream/transform/transformMapSync.js +3 -3
  29. package/dist/stream/transform/transformTap.d.ts +5 -2
  30. package/dist/stream/transform/transformTap.js +2 -1
  31. package/dist/stream/transform/worker/workerClassProxy.js +1 -0
  32. package/dist/stream/writable/writableFork.d.ts +2 -0
  33. package/dist/stream/writable/writableFork.js +2 -0
  34. package/dist/stream/writable/writableLimit.d.ts +9 -0
  35. package/dist/stream/writable/writableLimit.js +29 -0
  36. package/dist/stream/writable/writableVoid.d.ts +8 -1
  37. package/dist/stream/writable/writableVoid.js +5 -1
  38. package/package.json +1 -1
  39. package/src/index.ts +17 -156
  40. package/src/log/log.util.ts +9 -0
  41. package/src/stream/ndjson/ndjsonMap.ts +7 -5
  42. package/src/stream/ndjson/ndjsonStreamForEach.ts +2 -2
  43. package/src/stream/pipeline/pipeline.ts +102 -9
  44. package/src/stream/readable/readableCreate.ts +9 -1
  45. package/src/stream/readable/readableForEach.ts +2 -2
  46. package/src/stream/readable/readableFromArray.ts +18 -21
  47. package/src/stream/readable/readableMap.ts +24 -21
  48. package/src/stream/stream.util.ts +29 -0
  49. package/src/stream/transform/transformLimit.ts +71 -19
  50. package/src/stream/transform/transformLogProgress.ts +7 -4
  51. package/src/stream/transform/transformMap.ts +74 -82
  52. package/src/stream/transform/transformMapSimple.ts +5 -3
  53. package/src/stream/transform/transformMapSync.ts +6 -3
  54. package/src/stream/transform/transformTap.ts +8 -3
  55. package/src/stream/transform/worker/workerClassProxy.js +1 -0
  56. package/src/stream/writable/writableFork.ts +2 -0
  57. package/src/stream/writable/writableLimit.ts +28 -0
  58. package/src/stream/writable/writableVoid.ts +13 -1
  59. package/dist/stream/transform/legacy/transformMap.d.ts +0 -17
  60. package/dist/stream/transform/legacy/transformMap.js +0 -94
  61. package/src/stream/transform/legacy/transformMap.ts +0 -133
@@ -1,32 +1,84 @@
1
- import { Transform } from 'stream'
1
+ import { Readable } from 'stream'
2
+ import { CommonLogger } from '@naturalcycles/js-lib'
3
+ import { AbortableTransform, transformNoOp } from '../../index'
2
4
  import { TransformOptions, TransformTyped } from '../stream.model'
5
+ import { pipelineClose } from '../stream.util'
6
+
7
+ export interface TransformLimitOptions extends TransformOptions {
8
+ /**
9
+ * Nullish value (e.g 0 or undefined) would mean "no limit"
10
+ */
11
+ limit?: number
12
+
13
+ /**
14
+ * If provided (recommended!) - it will call readable.destroy() on limit.
15
+ * Without it - it will only stop the downstream consumers, but won't stop
16
+ * the Readable ("source" of the stream).
17
+ * It is almost always crucial to stop the Source too, so, please provide the Readable here!
18
+ */
19
+ sourceReadable?: Readable
20
+
21
+ /**
22
+ * Please provide it (a Promise that resolves when the Stream is done, e.g finished consuming things)
23
+ * to be able to wait for Consumers before calling `readable.destroy`.
24
+ * Has no effect if `readable` is not provided.
25
+ */
26
+ streamDone?: Promise<void>
27
+
28
+ logger?: CommonLogger
29
+
30
+ /**
31
+ * Set to true to enable additional debug messages, e.g it'll log
32
+ * when readable still emits values after the limit is reached.
33
+ */
34
+ debug?: boolean
35
+ }
36
+
37
+ /**
38
+ * Class only exists to be able to do `instanceof TransformLimit`
39
+ * and to set sourceReadable+streamDone to it in `_pipeline`.
40
+ */
41
+ export class TransformLimit extends AbortableTransform {}
3
42
 
4
43
  /**
5
44
  * 0 or falsy value means "no limit"
6
45
  */
7
- export function transformLimit<IN>(
8
- limit?: number,
9
- opt: TransformOptions = {},
10
- ): TransformTyped<IN, IN> {
11
- let index = 0
12
- let ended = false
46
+ export function transformLimit<IN>(opt: TransformLimitOptions = {}): TransformTyped<IN, IN> {
47
+ const { logger = console, limit, debug } = opt
48
+
49
+ if (!limit) {
50
+ // No limit - returning pass-through transform
51
+ return transformNoOp()
52
+ }
13
53
 
14
- return new Transform({
54
+ let i = 0 // so we start first chunk with 1
55
+ let ended = false
56
+ return new TransformLimit({
15
57
  objectMode: true,
16
58
  ...opt,
17
- transform(this: Transform, chunk: IN, _encoding, cb) {
18
- index++
59
+ transform(this: TransformLimit, chunk, _, cb) {
60
+ i++
19
61
 
20
- if (!ended) {
21
- cb(null, chunk) // pass through the item
22
- } else {
23
- cb(null) // pass-through empty
24
- }
25
-
26
- if (limit && index === limit) {
62
+ if (i === limit) {
27
63
  ended = true
28
- console.log(`transformLimit: limit of ${limit} reached`)
29
- // this.emit('end') // this makes it "halt" on Node 14 lts
64
+ logger.log(`transformLimit of ${limit} reached`)
65
+ this.push(chunk)
66
+
67
+ pipelineClose(
68
+ 'transformLimit',
69
+ this,
70
+ opt.sourceReadable || this.sourceReadable,
71
+ opt.streamDone || this.streamDone,
72
+ logger,
73
+ )
74
+
75
+ cb() // after pause
76
+ } else if (!ended) {
77
+ cb(null, chunk)
78
+ } else {
79
+ if (debug) logger.log(`transformLimit.transform after limit`, i)
80
+ // If we ever HANG (don't call cb) - Node will do process.exit(0) to us
81
+ cb() // ended, don't emit anything
30
82
  }
31
83
  },
32
84
  })
@@ -1,6 +1,6 @@
1
1
  import { Transform } from 'stream'
2
2
  import { inspect, InspectOptions } from 'util'
3
- import { SimpleMovingAverage, _mb, _since, AnyObject } from '@naturalcycles/js-lib'
3
+ import { SimpleMovingAverage, _mb, _since, AnyObject, CommonLogger } from '@naturalcycles/js-lib'
4
4
  import { dayjs } from '@naturalcycles/time-lib'
5
5
  import { boldWhite, dimGrey, white, yellow } from '../../colors'
6
6
  import { hasColors } from '../../colors/colors'
@@ -86,6 +86,8 @@ export interface TransformLogProgressOptions<IN = any> extends TransformOptions
86
86
  */
87
87
  logEvery?: number
88
88
 
89
+ logger?: CommonLogger
90
+
89
91
  /**
90
92
  * Function to return extra properties to the "progress object".
91
93
  *
@@ -124,6 +126,7 @@ export function transformLogProgress<IN = any>(
124
126
  logEvery = 1000,
125
127
  batchSize = 1,
126
128
  extra,
129
+ logger = console,
127
130
  } = opt
128
131
  const logProgress = opt.logProgress !== false && logEvery !== 0 // true by default
129
132
  const logEvery10 = logEvery * 10
@@ -172,7 +175,7 @@ export function transformLogProgress<IN = any>(
172
175
  const rps10 = Math.round(sma.push(lastRPS))
173
176
  if (mem.rss > peakRSS) peakRSS = mem.rss
174
177
 
175
- console.log(
178
+ logger.log(
176
179
  inspect(
177
180
  {
178
181
  [final ? `${metric}_final` : metric]: batchedProgress,
@@ -202,13 +205,13 @@ export function transformLogProgress<IN = any>(
202
205
  perHour = Math.round(perHour / 1000) + 'K'
203
206
  }
204
207
 
205
- console.log(
208
+ logger.log(
206
209
  `${dimGrey(dayjs().toPretty())} ${white(metric)} took ${yellow(
207
210
  _since(started),
208
211
  )} so far to process ${yellow(batchedProgress)} rows, ~${yellow(perHour)}/hour`,
209
212
  )
210
213
  } else if (final) {
211
- console.log(
214
+ logger.log(
212
215
  `${boldWhite(metric)} took ${yellow(_since(started))} to process ${yellow(
213
216
  batchedProgress,
214
217
  )} rows with total RPS of ${yellow(rpsTotal)}`,
@@ -1,15 +1,18 @@
1
- import { Transform } from 'stream'
2
1
  import {
2
+ AbortableAsyncMapper,
3
3
  AggregatedError,
4
- AsyncMapper,
5
4
  AsyncPredicate,
6
5
  CommonLogger,
6
+ END,
7
7
  ErrorMode,
8
8
  pFilter,
9
- PQueue,
9
+ SKIP,
10
10
  } from '@naturalcycles/js-lib'
11
+ import through2Concurrent = require('through2-concurrent')
11
12
  import { yellow } from '../../colors'
13
+ import { AbortableTransform } from '../pipeline/pipeline'
12
14
  import { TransformTyped } from '../stream.model'
15
+ import { pipelineClose } from '../stream.util'
13
16
 
14
17
  export interface TransformMapOptions<IN = any, OUT = IN> {
15
18
  /**
@@ -54,11 +57,6 @@ export interface TransformMapOptions<IN = any, OUT = IN> {
54
57
  */
55
58
  metric?: string
56
59
 
57
- /**
58
- * If defined - called BEFORE `final()` callback is called.
59
- */
60
- beforeFinal?: () => any
61
-
62
60
  logger?: CommonLogger
63
61
  }
64
62
 
@@ -66,6 +64,9 @@ export function notNullishPredicate(item: any): boolean {
66
64
  return item !== undefined && item !== null
67
65
  }
68
66
 
67
+ // doesn't work, cause here we don't construct our Transform instance ourselves
68
+ // export class TransformMap extends AbortableTransform {}
69
+
69
70
  /**
70
71
  * Like pMap, but for streams.
71
72
  * Inspired by `through2`.
@@ -79,7 +80,7 @@ export function notNullishPredicate(item: any): boolean {
79
80
  * If an Array is returned by `mapper` - it will be flattened and multiple results will be emitted from it. Tested by Array.isArray().
80
81
  */
81
82
  export function transformMap<IN = any, OUT = IN>(
82
- mapper: AsyncMapper<IN, OUT>,
83
+ mapper: AbortableAsyncMapper<IN, OUT>,
83
84
  opt: TransformMapOptions<IN, OUT> = {},
84
85
  ): TransformTyped<IN, OUT> {
85
86
  const {
@@ -88,98 +89,89 @@ export function transformMap<IN = any, OUT = IN>(
88
89
  errorMode = ErrorMode.THROW_IMMEDIATELY,
89
90
  flattenArrayOutput,
90
91
  onError,
91
- beforeFinal,
92
92
  metric = 'stream',
93
93
  logger = console,
94
94
  } = opt
95
95
 
96
96
  let index = -1
97
- let isRejected = false
97
+ let isSettled = false
98
98
  let errors = 0
99
99
  const collectedErrors: Error[] = [] // only used if errorMode == THROW_AGGREGATED
100
100
 
101
- const q = new PQueue({
102
- concurrency,
103
- resolveOn: 'start',
104
- // debug: true,
105
- })
106
-
107
- return new Transform({
108
- objectMode: true,
101
+ return through2Concurrent.obj(
102
+ {
103
+ maxConcurrency: concurrency,
104
+ async final(cb) {
105
+ // console.log('transformMap final')
109
106
 
110
- async final(cb) {
111
- // console.log('transformMap final', {index}, q.inFlight, q.queueSize)
107
+ logErrorStats(true)
112
108
 
113
- // wait for the current inFlight jobs to complete and push their results
114
- await q.onIdle()
109
+ if (collectedErrors.length) {
110
+ // emit Aggregated error
111
+ cb(new AggregatedError(collectedErrors))
112
+ } else {
113
+ // emit no error
114
+ cb()
115
+ }
116
+ },
117
+ },
118
+ async function transformMapFn(this: AbortableTransform, chunk: IN, _, cb) {
119
+ index++
120
+ // console.log({chunk, _encoding})
121
+
122
+ // Stop processing if isSettled (either THROW_IMMEDIATELY was fired or END received)
123
+ if (isSettled) return cb()
124
+
125
+ try {
126
+ 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)
127
+ const res = await mapper(chunk, currentIndex)
128
+ const passedResults = await pFilter(
129
+ flattenArrayOutput && Array.isArray(res) ? res : [res],
130
+ async r => {
131
+ if (r === END) {
132
+ isSettled = true // will be checked later
133
+ return false
134
+ }
135
+ return r !== SKIP && (await predicate(r, currentIndex))
136
+ },
137
+ )
138
+
139
+ passedResults.forEach(r => this.push(r))
140
+
141
+ if (isSettled) {
142
+ logger.log(`transformMap END received at index ${index}`)
143
+ pipelineClose('transformMap', this, this.sourceReadable, this.streamDone, logger)
144
+ }
115
145
 
116
- logErrorStats(logger, true)
146
+ cb() // done processing
147
+ } catch (err) {
148
+ logger.error(err)
149
+ errors++
150
+ logErrorStats()
117
151
 
118
- await beforeFinal?.() // call beforeFinal if defined
152
+ if (onError) {
153
+ try {
154
+ onError(err, chunk)
155
+ } catch {}
156
+ }
119
157
 
120
- if (collectedErrors.length) {
121
- // emit Aggregated error
122
- cb(new AggregatedError(collectedErrors))
123
- } else {
124
- // emit no error
125
- cb()
126
- }
127
- },
158
+ if (errorMode === ErrorMode.THROW_IMMEDIATELY) {
159
+ isSettled = true
160
+ return cb(err) // Emit error immediately
161
+ }
128
162
 
129
- async transform(this: Transform, chunk: IN, _encoding, cb) {
130
- index++
131
- // console.log('transform', {index})
132
-
133
- // Stop processing if THROW_IMMEDIATELY mode is used
134
- if (isRejected && errorMode === ErrorMode.THROW_IMMEDIATELY) return cb()
135
-
136
- // It resolves when it is successfully STARTED execution.
137
- // If it's queued instead - it'll wait and resolve only upon START.
138
- await q.push(async () => {
139
- try {
140
- 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)
141
- const res = await mapper(chunk, currentIndex)
142
- const passedResults = await pFilter(
143
- flattenArrayOutput && Array.isArray(res) ? res : [res],
144
- async r => await predicate(r, currentIndex),
145
- )
146
-
147
- passedResults.forEach(r => this.push(r))
148
- } catch (err) {
149
- logger.error(err)
150
-
151
- errors++
152
-
153
- logErrorStats(logger)
154
-
155
- if (onError) {
156
- try {
157
- onError(err, chunk)
158
- } catch {}
159
- }
160
-
161
- if (errorMode === ErrorMode.THROW_IMMEDIATELY) {
162
- isRejected = true
163
- // Emit error immediately
164
- // return cb(err as Error)
165
- return this.emit('error', err as Error)
166
- }
167
-
168
- if (errorMode === ErrorMode.THROW_AGGREGATED) {
169
- collectedErrors.push(err as Error)
170
- }
163
+ if (errorMode === ErrorMode.THROW_AGGREGATED) {
164
+ collectedErrors.push(err as Error)
171
165
  }
172
- })
173
166
 
174
- // Resolved, which means it STARTED processing
175
- // This means we can take more load
176
- cb()
167
+ // Tell input stream that we're done processing, but emit nothing to output - not error nor result
168
+ cb()
169
+ }
177
170
  },
178
- })
171
+ )
179
172
 
180
- function logErrorStats(logger: CommonLogger, final = false): void {
173
+ function logErrorStats(final = false): void {
181
174
  if (!errors) return
182
-
183
175
  logger.log(`${metric} ${final ? 'final ' : ''}errors: ${yellow(errors)}`)
184
176
  }
185
177
  }
@@ -1,5 +1,5 @@
1
1
  import { Transform } from 'stream'
2
- import { ErrorMode, Mapper } from '@naturalcycles/js-lib'
2
+ import { CommonLogger, ErrorMode, Mapper } from '@naturalcycles/js-lib'
3
3
  import { TransformTyped } from '../stream.model'
4
4
 
5
5
  export interface TransformMapSimpleOptions {
@@ -9,6 +9,8 @@ export interface TransformMapSimpleOptions {
9
9
  * @default ErrorMode.THROW_IMMEDIATELY
10
10
  */
11
11
  errorMode?: ErrorMode.THROW_IMMEDIATELY | ErrorMode.SUPPRESS
12
+
13
+ logger?: CommonLogger
12
14
  }
13
15
 
14
16
  /**
@@ -25,7 +27,7 @@ export function transformMapSimple<IN = any, OUT = IN>(
25
27
  opt: TransformMapSimpleOptions = {},
26
28
  ): TransformTyped<IN, OUT> {
27
29
  let index = -1
28
- const { errorMode = ErrorMode.THROW_IMMEDIATELY } = opt
30
+ const { errorMode = ErrorMode.THROW_IMMEDIATELY, logger = console } = opt
29
31
 
30
32
  return new Transform({
31
33
  objectMode: true,
@@ -33,7 +35,7 @@ export function transformMapSimple<IN = any, OUT = IN>(
33
35
  try {
34
36
  cb(null, mapper(chunk, ++index))
35
37
  } catch (err) {
36
- console.error(err)
38
+ logger.error(err)
37
39
 
38
40
  if (errorMode === ErrorMode.SUPPRESS) {
39
41
  cb() // suppress the error
@@ -1,5 +1,5 @@
1
1
  import { Transform } from 'stream'
2
- import { AggregatedError, ErrorMode, Mapper, Predicate } from '@naturalcycles/js-lib'
2
+ import { AggregatedError, CommonLogger, ErrorMode, Mapper, Predicate } from '@naturalcycles/js-lib'
3
3
  import { yellow } from '../../colors'
4
4
  import { TransformTyped } from '../stream.model'
5
5
  import { notNullishPredicate } from './transformMap'
@@ -43,6 +43,8 @@ export interface TransformMapSyncOptions<IN = any, OUT = IN> {
43
43
  * @default `stream`
44
44
  */
45
45
  metric?: string
46
+
47
+ logger?: CommonLogger
46
48
  }
47
49
 
48
50
  /**
@@ -62,6 +64,7 @@ export function transformMapSync<IN = any, OUT = IN>(
62
64
  onError,
63
65
  metric = 'stream',
64
66
  objectMode = true,
67
+ logger = console,
65
68
  } = opt
66
69
  let isRejected = false
67
70
  let errors = 0
@@ -92,7 +95,7 @@ export function transformMapSync<IN = any, OUT = IN>(
92
95
  cb(null, v)
93
96
  }
94
97
  } catch (err) {
95
- console.error(err)
98
+ logger.error(err)
96
99
  errors++
97
100
 
98
101
  logErrorStats()
@@ -134,6 +137,6 @@ export function transformMapSync<IN = any, OUT = IN>(
134
137
  function logErrorStats(final = false): void {
135
138
  if (!errors) return
136
139
 
137
- console.log(`${metric} ${final ? 'final ' : ''}errors: ${yellow(errors)}`)
140
+ logger.log(`${metric} ${final ? 'final ' : ''}errors: ${yellow(errors)}`)
138
141
  }
139
142
  }
@@ -1,7 +1,11 @@
1
1
  import { Transform } from 'stream'
2
- import { AsyncMapper } from '@naturalcycles/js-lib'
2
+ import { AsyncMapper, CommonLogger } from '@naturalcycles/js-lib'
3
3
  import { TransformOptions, TransformTyped } from '../stream.model'
4
4
 
5
+ export interface TransformTapOptions extends TransformOptions {
6
+ logger?: CommonLogger
7
+ }
8
+
5
9
  /**
6
10
  * Similar to RxJS `tap` - allows to run a function for each stream item, without affecting the result.
7
11
  * Item is passed through to the output.
@@ -10,8 +14,9 @@ import { TransformOptions, TransformTyped } from '../stream.model'
10
14
  */
11
15
  export function transformTap<IN>(
12
16
  fn: AsyncMapper<IN, any>,
13
- opt: TransformOptions = {},
17
+ opt: TransformTapOptions = {},
14
18
  ): TransformTyped<IN, IN> {
19
+ const { logger = console } = opt
15
20
  let index = 0
16
21
 
17
22
  return new Transform({
@@ -23,7 +28,7 @@ export function transformTap<IN>(
23
28
  try {
24
29
  await fn(chunk, index++)
25
30
  } catch (err) {
26
- console.error(err)
31
+ logger.error(err)
27
32
  // suppressed error
28
33
  }
29
34
 
@@ -10,6 +10,7 @@ if (!workerFile) {
10
10
  // console.log(`worker#${workerIndex} created`)
11
11
 
12
12
  try {
13
+ // require('esbuild-register') // alternative
13
14
  require('ts-node/register/transpile-only')
14
15
  require('tsconfig-paths/register')
15
16
  } catch {} // require if exists
@@ -7,6 +7,8 @@ import { TransformOptions, WritableTyped } from '../stream.model'
7
7
  * Currently does NOT (!) maintain backpressure.
8
8
  * Error in the forked pipeline will propagate up to the main pipeline (and log error, to be sure).
9
9
  * Will wait until all forked pipelines are completed before completing the stream.
10
+ *
11
+ * @experimental
10
12
  */
11
13
  export function writableFork<T>(
12
14
  chains: NodeJS.WritableStream[][],
@@ -0,0 +1,28 @@
1
+ import { Readable, Writable } from 'stream'
2
+ import { WritableTyped } from '../stream.model'
3
+
4
+ /**
5
+ * Allows to stop the Readable stream after the pipeline has processed X number of rows.
6
+ * It counts OUTPUT rows (not input), because this Writable is always at the end of the Pipeline.
7
+ * It ensures that everything has been processed before issuing a STOP on the readable.
8
+ */
9
+ export function writableLimit<T>(readable: Readable, limit: number): WritableTyped<T> {
10
+ let i = 0
11
+
12
+ return new Writable({
13
+ objectMode: true,
14
+ write(chunk, _, cb) {
15
+ if (limit === 0) return cb() // no limit, just passthrough
16
+
17
+ i++
18
+
19
+ if (i === limit) {
20
+ console.log(`writableLimit of ${limit} reached`)
21
+ readable.destroy()
22
+ cb() // do we need it?
23
+ } else {
24
+ cb() // passthrough
25
+ }
26
+ },
27
+ })
28
+ }
@@ -1,17 +1,29 @@
1
1
  import { Writable } from 'stream'
2
+ import { DeferredPromise } from '@naturalcycles/js-lib'
2
3
  import { TransformOptions } from '../stream.model'
3
4
 
5
+ export interface WritableVoidOptions extends TransformOptions {
6
+ /**
7
+ * If set - it will be Resolved when the Stream is done (after final.cb)
8
+ */
9
+ streamDone?: DeferredPromise
10
+ }
11
+
4
12
  /**
5
13
  * Use as a "null-terminator" of stream.pipeline.
6
14
  * It consumes the stream as quickly as possible without doing anything.
7
15
  * Put it in the end of your pipeline in case it ends with Transform that needs a consumer.
8
16
  */
9
- export function writableVoid(opt?: TransformOptions): Writable {
17
+ export function writableVoid(opt: WritableVoidOptions = {}): Writable {
10
18
  return new Writable({
11
19
  objectMode: true,
12
20
  ...opt,
13
21
  write(chunk, _encoding, cb) {
14
22
  cb()
15
23
  },
24
+ final(cb) {
25
+ cb()
26
+ opt.streamDone?.resolve()
27
+ },
16
28
  })
17
29
  }
@@ -1,17 +0,0 @@
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 transformMapLegacy<IN = any, OUT = IN>(mapper: AsyncMapper<IN, OUT>, opt?: TransformMapOptions<IN, OUT>): TransformTyped<IN, OUT>;
@@ -1,94 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.transformMapLegacy = exports.notNullishPredicate = void 0;
4
- const js_lib_1 = require("@naturalcycles/js-lib");
5
- const through2Concurrent = require("through2-concurrent");
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 transformMapLegacy(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
- return through2Concurrent.obj({
30
- maxConcurrency: concurrency,
31
- // autoDestroy: true,
32
- async final(cb) {
33
- // console.log('transformMap final')
34
- logErrorStats(logger, true);
35
- await beforeFinal?.(); // call beforeFinal if defined
36
- if (collectedErrors.length) {
37
- // emit Aggregated error
38
- cb(new js_lib_1.AggregatedError(collectedErrors));
39
- }
40
- else {
41
- // emit no error
42
- cb();
43
- }
44
- },
45
- }, async function transformMapFn(chunk, _encoding, cb) {
46
- index++;
47
- // console.log({chunk, _encoding})
48
- // Stop processing if THROW_IMMEDIATELY mode is used
49
- if (isRejected && errorMode === js_lib_1.ErrorMode.THROW_IMMEDIATELY)
50
- return cb();
51
- try {
52
- 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)
53
- const res = await mapper(chunk, currentIndex);
54
- const passedResults = await (0, js_lib_1.pFilter)(flattenArrayOutput && Array.isArray(res) ? res : [res], async (r) => await predicate(r, currentIndex));
55
- if (passedResults.length === 0) {
56
- cb(); // 0 results
57
- }
58
- else {
59
- passedResults.forEach(r => {
60
- this.push(r);
61
- // cb(null, r)
62
- });
63
- cb(); // done processing
64
- }
65
- }
66
- catch (err) {
67
- logger.error(err);
68
- errors++;
69
- logErrorStats(logger);
70
- if (onError) {
71
- try {
72
- onError(err, chunk);
73
- }
74
- catch { }
75
- }
76
- if (errorMode === js_lib_1.ErrorMode.THROW_IMMEDIATELY) {
77
- isRejected = true;
78
- // Emit error immediately
79
- return cb(err);
80
- }
81
- if (errorMode === js_lib_1.ErrorMode.THROW_AGGREGATED) {
82
- collectedErrors.push(err);
83
- }
84
- // Tell input stream that we're done processing, but emit nothing to output - not error nor result
85
- cb();
86
- }
87
- });
88
- function logErrorStats(logger, final = false) {
89
- if (!errors)
90
- return;
91
- logger.log(`${metric} ${final ? 'final ' : ''}errors: ${(0, colors_1.yellow)(errors)}`);
92
- }
93
- }
94
- exports.transformMapLegacy = transformMapLegacy;