@naturalcycles/nodejs-lib 12.57.0 → 12.61.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 (87) hide show
  1. package/dist/got/getGot.js +98 -28
  2. package/dist/got/got.model.d.ts +6 -0
  3. package/dist/index.d.ts +37 -36
  4. package/dist/index.js +36 -129
  5. package/dist/log/log.util.d.ts +4 -0
  6. package/dist/log/log.util.js +11 -0
  7. package/dist/stream/ndjson/ndjsonMap.d.ts +2 -2
  8. package/dist/stream/ndjson/ndjsonMap.js +4 -3
  9. package/dist/stream/ndjson/ndjsonStreamForEach.d.ts +2 -2
  10. package/dist/stream/ndjson/transformJsonParse.js +3 -3
  11. package/dist/stream/ndjson/transformToNDJson.js +2 -2
  12. package/dist/stream/pipeline/pipeline.d.ts +25 -3
  13. package/dist/stream/pipeline/pipeline.js +76 -9
  14. package/dist/stream/readable/readableCreate.d.ts +8 -0
  15. package/dist/stream/readable/readableCreate.js +9 -1
  16. package/dist/stream/readable/readableForEach.d.ts +2 -2
  17. package/dist/stream/readable/readableFromArray.d.ts +2 -2
  18. package/dist/stream/readable/readableFromArray.js +17 -13
  19. package/dist/stream/readable/readableMap.d.ts +2 -2
  20. package/dist/stream/readable/readableMap.js +22 -17
  21. package/dist/stream/sizeStack.d.ts +9 -0
  22. package/dist/stream/sizeStack.js +48 -0
  23. package/dist/stream/stream.util.d.ts +4 -0
  24. package/dist/stream/stream.util.js +24 -0
  25. package/dist/stream/transform/transformBuffer.js +1 -1
  26. package/dist/stream/transform/transformFilter.d.ts +3 -4
  27. package/dist/stream/transform/transformFilter.js +5 -20
  28. package/dist/stream/transform/transformLimit.d.ts +32 -1
  29. package/dist/stream/transform/transformLimit.js +33 -16
  30. package/dist/stream/transform/transformLogProgress.d.ts +20 -0
  31. package/dist/stream/transform/transformLogProgress.js +36 -18
  32. package/dist/stream/transform/transformMap.d.ts +4 -10
  33. package/dist/stream/transform/transformMap.js +52 -64
  34. package/dist/stream/transform/transformMapSimple.js +1 -1
  35. package/dist/stream/transform/transformMapSync.d.ts +5 -3
  36. package/dist/stream/transform/transformMapSync.js +28 -22
  37. package/dist/stream/transform/transformNoOp.js +1 -1
  38. package/dist/stream/transform/transformTap.js +3 -3
  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.js +1 -1
  44. package/dist/stream/writable/writableLimit.d.ts +9 -0
  45. package/dist/stream/writable/writableLimit.js +29 -0
  46. package/dist/stream/writable/writablePushToArray.js +1 -1
  47. package/dist/stream/writable/writableVoid.d.ts +8 -1
  48. package/dist/stream/writable/writableVoid.js +6 -2
  49. package/dist/util/zip.util.d.ts +10 -2
  50. package/dist/util/zip.util.js +10 -3
  51. package/package.json +1 -1
  52. package/src/got/getGot.ts +120 -31
  53. package/src/got/got.model.ts +8 -0
  54. package/src/index.ts +36 -194
  55. package/src/log/log.util.ts +9 -0
  56. package/src/stream/ndjson/ndjsonMap.ts +7 -5
  57. package/src/stream/ndjson/ndjsonStreamForEach.ts +2 -2
  58. package/src/stream/ndjson/transformJsonParse.ts +3 -3
  59. package/src/stream/ndjson/transformToNDJson.ts +2 -2
  60. package/src/stream/pipeline/pipeline.ts +102 -9
  61. package/src/stream/readable/readableCreate.ts +9 -1
  62. package/src/stream/readable/readableForEach.ts +2 -2
  63. package/src/stream/readable/readableFromArray.ts +18 -21
  64. package/src/stream/readable/readableMap.ts +24 -21
  65. package/src/stream/sizeStack.ts +56 -0
  66. package/src/stream/stream.util.ts +29 -0
  67. package/src/stream/transform/transformBuffer.ts +1 -1
  68. package/src/stream/transform/transformFilter.ts +6 -20
  69. package/src/stream/transform/transformLimit.ts +66 -20
  70. package/src/stream/transform/transformLogProgress.ts +72 -23
  71. package/src/stream/transform/transformMap.ts +74 -94
  72. package/src/stream/transform/transformMapSimple.ts +1 -1
  73. package/src/stream/transform/transformMapSync.ts +40 -26
  74. package/src/stream/transform/transformNoOp.ts +1 -1
  75. package/src/stream/transform/transformTap.ts +3 -3
  76. package/src/stream/transform/transformToArray.ts +1 -1
  77. package/src/stream/transform/transformToString.ts +2 -2
  78. package/src/stream/transform/worker/transformMultiThreaded.ts +1 -1
  79. package/src/stream/transform/worker/workerClassProxy.js +1 -0
  80. package/src/stream/writable/writableFork.ts +1 -1
  81. package/src/stream/writable/writableLimit.ts +28 -0
  82. package/src/stream/writable/writablePushToArray.ts +1 -1
  83. package/src/stream/writable/writableVoid.ts +14 -2
  84. package/src/util/zip.util.ts +11 -3
  85. package/dist/stream/transform/legacy/transformMap.d.ts +0 -17
  86. package/dist/stream/transform/legacy/transformMap.js +0 -94
  87. package/src/stream/transform/legacy/transformMap.ts +0 -133
@@ -4,6 +4,7 @@ import { SimpleMovingAverage, _mb, _since, AnyObject, CommonLogger } from '@natu
4
4
  import { dayjs } from '@naturalcycles/time-lib'
5
5
  import { boldWhite, dimGrey, white, yellow } from '../../colors'
6
6
  import { hasColors } from '../../colors/colors'
7
+ import { SizeStack } from '../sizeStack'
7
8
  import { TransformOptions, TransformTyped } from '../stream.model'
8
9
 
9
10
  export interface TransformLogProgressOptions<IN = any> extends TransformOptions {
@@ -103,6 +104,41 @@ export interface TransformLogProgressOptions<IN = any> extends TransformOptions
103
104
  * Defaults to 1.
104
105
  */
105
106
  batchSize?: number
107
+
108
+ /**
109
+ * Experimental logging of item (shunk) sizes, when json-stringified.
110
+ *
111
+ * Defaults to false.
112
+ *
113
+ * @experimental
114
+ */
115
+ logSizes?: boolean
116
+
117
+ /**
118
+ * How many last item sizes to keep in a buffer, to calculate stats (p50, p90, avg, etc).
119
+ * Defaults to 100_000.
120
+ * Cannot be Infinity.
121
+ */
122
+ logSizesBuffer?: number
123
+
124
+ /**
125
+ * Works in addition to `logSizes`. Adds "zipped sizes".
126
+ *
127
+ * @experimental
128
+ */
129
+ logZippedSizes?: boolean
130
+ }
131
+
132
+ interface LogItem extends AnyObject {
133
+ heapUsed?: number
134
+ heapTotal?: number
135
+ rss?: number
136
+ peakRSS?: number
137
+ rssMinusHeap?: number
138
+ external?: number
139
+ arrayBuffers?: number
140
+ rps10?: number
141
+ rpsTotal?: number
106
142
  }
107
143
 
108
144
  const inspectOpt: InspectOptions = {
@@ -124,6 +160,9 @@ export function transformLogProgress<IN = any>(
124
160
  peakRSS: logPeakRSS = true,
125
161
  logRPS = true,
126
162
  logEvery = 1000,
163
+ logSizes = false,
164
+ logSizesBuffer = 100_000,
165
+ logZippedSizes = false,
127
166
  batchSize = 1,
128
167
  extra,
129
168
  logger = console,
@@ -138,15 +177,23 @@ export function transformLogProgress<IN = any>(
138
177
  let progress = 0
139
178
  let peakRSS = 0
140
179
 
180
+ const sizes = logSizes ? new SizeStack('json', logSizesBuffer) : undefined
181
+ const sizesZipped = logZippedSizes ? new SizeStack('json.gz', logSizesBuffer) : undefined
182
+
141
183
  logStats() // initial
142
184
 
143
185
  return new Transform({
144
186
  objectMode: true,
145
187
  ...opt,
146
- transform(chunk: IN, _encoding, cb) {
188
+ transform(chunk: IN, _, cb) {
147
189
  progress++
148
190
  processedLastSecond++
149
191
 
192
+ if (sizes) {
193
+ // Check it, cause gzipping might be delayed here..
194
+ void SizeStack.countItem(chunk, logger, sizes, sizesZipped)
195
+ }
196
+
150
197
  if (logProgress && progress % logEvery === 0) {
151
198
  logStats(chunk, false, progress % logEvery10 === 0)
152
199
  }
@@ -175,28 +222,30 @@ export function transformLogProgress<IN = any>(
175
222
  const rps10 = Math.round(sma.push(lastRPS))
176
223
  if (mem.rss > peakRSS) peakRSS = mem.rss
177
224
 
178
- logger.log(
179
- inspect(
180
- {
181
- [final ? `${metric}_final` : metric]: batchedProgress,
182
- ...(extra ? extra(chunk, progress) : {}),
183
- ...(logHeapUsed ? { heapUsed: _mb(mem.heapUsed) } : {}),
184
- ...(logHeapTotal ? { heapTotal: _mb(mem.heapTotal) } : {}),
185
- ...(logRss ? { rss: _mb(mem.rss) } : {}),
186
- ...(logPeakRSS ? { peakRSS: _mb(peakRSS) } : {}),
187
- ...(opt.rssMinusHeap ? { rssMinusHeap: _mb(mem.rss - mem.heapTotal) } : {}),
188
- ...(opt.external ? { external: _mb(mem.external) } : {}),
189
- ...(opt.arrayBuffers ? { arrayBuffers: _mb(mem.arrayBuffers || 0) } : {}),
190
- ...(logRPS
191
- ? {
192
- rps10,
193
- rpsTotal,
194
- }
195
- : {}),
196
- },
197
- inspectOpt,
198
- ),
199
- )
225
+ const o: LogItem = {
226
+ [final ? `${metric}_final` : metric]: batchedProgress,
227
+ }
228
+
229
+ if (extra) Object.assign(o, extra(chunk, progress))
230
+ if (logHeapUsed) o.heapUsed = _mb(mem.heapUsed)
231
+ if (logHeapTotal) o.heapTotal = _mb(mem.heapTotal)
232
+ if (logRss) o.rss = _mb(mem.rss)
233
+ if (logPeakRSS) o.peakRSS = _mb(peakRSS)
234
+ if (opt.rssMinusHeap) o.rssMinusHeap = _mb(mem.rss - mem.heapTotal)
235
+ if (opt.external) o.external = _mb(mem.external)
236
+ if (opt.arrayBuffers) o.arrayBuffers = _mb(mem.arrayBuffers || 0)
237
+
238
+ if (logRPS) Object.assign(o, { rps10, rpsTotal })
239
+
240
+ logger.log(inspect(o, inspectOpt))
241
+
242
+ if (sizes?.items.length) {
243
+ logger.log(sizes.getStats())
244
+
245
+ if (sizesZipped?.items.length) {
246
+ logger.log(sizesZipped.getStats())
247
+ }
248
+ }
200
249
 
201
250
  if (tenx) {
202
251
  let perHour: number | string =
@@ -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
  /**
@@ -23,9 +26,8 @@ export interface TransformMapOptions<IN = any, OUT = IN> {
23
26
  * Predicate to filter outgoing results (after mapper).
24
27
  * Allows to not emit all results.
25
28
  *
26
- * Set to `r => r` (passthrough predicate) to pass ANY value (including undefined/null)
27
- *
28
- * @default to filter out undefined/null values, but pass anything else
29
+ * Defaults to "pass everything" (including null, undefined, etc).
30
+ * Simpler way to exclude certain cases is to return SKIP symbol from the mapper.
29
31
  */
30
32
  predicate?: AsyncPredicate<OUT>
31
33
 
@@ -54,17 +56,11 @@ export interface TransformMapOptions<IN = any, OUT = IN> {
54
56
  */
55
57
  metric?: string
56
58
 
57
- /**
58
- * If defined - called BEFORE `final()` callback is called.
59
- */
60
- beforeFinal?: () => any
61
-
62
59
  logger?: CommonLogger
63
60
  }
64
61
 
65
- export function notNullishPredicate(item: any): boolean {
66
- return item !== undefined && item !== null
67
- }
62
+ // doesn't work, cause here we don't construct our Transform instance ourselves
63
+ // export class TransformMap extends AbortableTransform {}
68
64
 
69
65
  /**
70
66
  * Like pMap, but for streams.
@@ -79,112 +75,96 @@ export function notNullishPredicate(item: any): boolean {
79
75
  * If an Array is returned by `mapper` - it will be flattened and multiple results will be emitted from it. Tested by Array.isArray().
80
76
  */
81
77
  export function transformMap<IN = any, OUT = IN>(
82
- mapper: AsyncMapper<IN, OUT>,
78
+ mapper: AbortableAsyncMapper<IN, OUT>,
83
79
  opt: TransformMapOptions<IN, OUT> = {},
84
80
  ): TransformTyped<IN, OUT> {
85
81
  const {
86
82
  concurrency = 16,
87
- predicate = notNullishPredicate,
83
+ predicate, // we now default to "no predicate" (meaning pass-everything)
88
84
  errorMode = ErrorMode.THROW_IMMEDIATELY,
89
85
  flattenArrayOutput,
90
86
  onError,
91
- beforeFinal,
92
87
  metric = 'stream',
93
88
  logger = console,
94
89
  } = opt
95
90
 
96
91
  let index = -1
97
- let isRejected = false
92
+ let isSettled = false
98
93
  let errors = 0
99
94
  const collectedErrors: Error[] = [] // only used if errorMode == THROW_AGGREGATED
100
95
 
101
- const q = new PQueue({
102
- concurrency,
103
- resolveOn: 'start',
104
- // debug: true,
105
- })
106
-
107
- return new Transform({
108
- objectMode: true,
96
+ return through2Concurrent.obj(
97
+ {
98
+ maxConcurrency: concurrency,
99
+ async final(cb) {
100
+ // console.log('transformMap final')
109
101
 
110
- async final(cb) {
111
- // console.log('transformMap final', {index}, q.inFlight, q.queueSize)
102
+ logErrorStats(true)
112
103
 
113
- // wait for the current inFlight jobs to complete and push their results
114
- await q.onIdle()
104
+ if (collectedErrors.length) {
105
+ // emit Aggregated error
106
+ cb(new AggregatedError(collectedErrors))
107
+ } else {
108
+ // emit no error
109
+ cb()
110
+ }
111
+ },
112
+ },
113
+ async function transformMapFn(this: AbortableTransform, chunk: IN, _, cb) {
114
+ // Stop processing if isSettled (either THROW_IMMEDIATELY was fired or END received)
115
+ if (isSettled) return cb()
116
+
117
+ const currentIndex = ++index
118
+
119
+ try {
120
+ const res = await mapper(chunk, currentIndex)
121
+ const passedResults = await pFilter(
122
+ flattenArrayOutput && Array.isArray(res) ? res : [res],
123
+ async r => {
124
+ if (r === END) {
125
+ isSettled = true // will be checked later
126
+ return false
127
+ }
128
+ return r !== SKIP && (!predicate || (await predicate(r, currentIndex)))
129
+ },
130
+ )
131
+
132
+ passedResults.forEach(r => this.push(r))
133
+
134
+ if (isSettled) {
135
+ logger.log(`transformMap END received at index ${currentIndex}`)
136
+ pipelineClose('transformMap', this, this.sourceReadable, this.streamDone, logger)
137
+ }
115
138
 
116
- logErrorStats(logger, true)
139
+ cb() // done processing
140
+ } catch (err) {
141
+ logger.error(err)
142
+ errors++
143
+ logErrorStats()
117
144
 
118
- await beforeFinal?.() // call beforeFinal if defined
145
+ if (onError) {
146
+ try {
147
+ onError(err, chunk)
148
+ } catch {}
149
+ }
119
150
 
120
- if (collectedErrors.length) {
121
- // emit Aggregated error
122
- // For the same reason, magically, let's not call `cb`, but emit an error event instead
123
- // this.emit('error', new AggregatedError(collectedErrors))
124
- cb(new AggregatedError(collectedErrors))
125
- } else {
126
- // emit no error
127
- // It is truly a mistery, but calling cb() here was causing ERR_MULTIPLE_CALLBACK ?!
128
- // Commenting it out seems to work ?!
129
- // ?!
130
- // cb()
131
- }
132
- },
151
+ if (errorMode === ErrorMode.THROW_IMMEDIATELY) {
152
+ isSettled = true
153
+ return cb(err) // Emit error immediately
154
+ }
133
155
 
134
- async transform(this: Transform, chunk: IN, _encoding, cb) {
135
- index++
136
- // console.log('transform', {index})
137
-
138
- // Stop processing if THROW_IMMEDIATELY mode is used
139
- if (isRejected && errorMode === ErrorMode.THROW_IMMEDIATELY) return cb()
140
-
141
- // It resolves when it is successfully STARTED execution.
142
- // If it's queued instead - it'll wait and resolve only upon START.
143
- await q.push(async () => {
144
- try {
145
- 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)
146
- const res = await mapper(chunk, currentIndex)
147
- const passedResults = await pFilter(
148
- flattenArrayOutput && Array.isArray(res) ? res : [res],
149
- async r => await predicate(r, currentIndex),
150
- )
151
-
152
- passedResults.forEach(r => this.push(r))
153
- } catch (err) {
154
- logger.error(err)
155
-
156
- errors++
157
-
158
- logErrorStats(logger)
159
-
160
- if (onError) {
161
- try {
162
- onError(err, chunk)
163
- } catch {}
164
- }
165
-
166
- if (errorMode === ErrorMode.THROW_IMMEDIATELY) {
167
- isRejected = true
168
- // Emit error immediately
169
- // return cb(err as Error)
170
- return this.emit('error', err as Error)
171
- }
172
-
173
- if (errorMode === ErrorMode.THROW_AGGREGATED) {
174
- collectedErrors.push(err as Error)
175
- }
156
+ if (errorMode === ErrorMode.THROW_AGGREGATED) {
157
+ collectedErrors.push(err as Error)
176
158
  }
177
- })
178
159
 
179
- // Resolved, which means it STARTED processing
180
- // This means we can take more load
181
- cb()
160
+ // Tell input stream that we're done processing, but emit nothing to output - not error nor result
161
+ cb()
162
+ }
182
163
  },
183
- })
164
+ )
184
165
 
185
- function logErrorStats(logger: CommonLogger, final = false): void {
166
+ function logErrorStats(final = false): void {
186
167
  if (!errors) return
187
-
188
168
  logger.log(`${metric} ${final ? 'final ' : ''}errors: ${yellow(errors)}`)
189
169
  }
190
170
  }
@@ -31,7 +31,7 @@ export function transformMapSimple<IN = any, OUT = IN>(
31
31
 
32
32
  return new Transform({
33
33
  objectMode: true,
34
- transform(chunk: IN, _encoding, cb) {
34
+ transform(chunk: IN, _, cb) {
35
35
  try {
36
36
  cb(null, mapper(chunk, ++index))
37
37
  } catch (err) {
@@ -1,8 +1,16 @@
1
- import { Transform } from 'stream'
2
- import { AggregatedError, CommonLogger, ErrorMode, Mapper, Predicate } from '@naturalcycles/js-lib'
1
+ import {
2
+ AggregatedError,
3
+ CommonLogger,
4
+ END,
5
+ ErrorMode,
6
+ Mapper,
7
+ Predicate,
8
+ SKIP,
9
+ } from '@naturalcycles/js-lib'
3
10
  import { yellow } from '../../colors'
11
+ import { AbortableTransform } from '../pipeline/pipeline'
4
12
  import { TransformTyped } from '../stream.model'
5
- import { notNullishPredicate } from './transformMap'
13
+ import { pipelineClose } from '../stream.util'
6
14
 
7
15
  export interface TransformMapSyncOptions<IN = any, OUT = IN> {
8
16
  /**
@@ -20,9 +28,8 @@ export interface TransformMapSyncOptions<IN = any, OUT = IN> {
20
28
  * Predicate to filter outgoing results (after mapper).
21
29
  * Allows to not emit all results.
22
30
  *
23
- * @default to filter out undefined/null values, but pass anything else
24
- *
25
- * Set to `r => r` (passthrough predicate) to pass ANY value (including undefined/null)
31
+ * Defaults to "pass everything".
32
+ * Simpler way to skip individual entries is to return SKIP symbol.
26
33
  */
27
34
  predicate?: Predicate<OUT>
28
35
 
@@ -47,6 +54,8 @@ export interface TransformMapSyncOptions<IN = any, OUT = IN> {
47
54
  logger?: CommonLogger
48
55
  }
49
56
 
57
+ export class TransformMapSync extends AbortableTransform {}
58
+
50
59
  /**
51
60
  * Sync (not async) version of transformMap.
52
61
  * Supposedly faster, for cases when async is not needed.
@@ -58,7 +67,7 @@ export function transformMapSync<IN = any, OUT = IN>(
58
67
  let index = -1
59
68
 
60
69
  const {
61
- predicate = notNullishPredicate,
70
+ predicate, // defaults to "no predicate" (pass everything)
62
71
  errorMode = ErrorMode.THROW_IMMEDIATELY,
63
72
  flattenArrayOutput = false,
64
73
  onError,
@@ -66,34 +75,39 @@ export function transformMapSync<IN = any, OUT = IN>(
66
75
  objectMode = true,
67
76
  logger = console,
68
77
  } = opt
69
- let isRejected = false
78
+ let isSettled = false
70
79
  let errors = 0
71
80
  const collectedErrors: Error[] = [] // only used if errorMode == THROW_AGGREGATED
72
81
 
73
- return new Transform({
82
+ return new TransformMapSync({
74
83
  objectMode,
75
84
  ...opt,
76
- transform(this: Transform, chunk: IN, _encoding, cb) {
77
- // Stop processing if THROW_IMMEDIATELY mode is used
78
- if (isRejected && errorMode === ErrorMode.THROW_IMMEDIATELY) {
79
- return cb()
80
- }
85
+ transform(this: AbortableTransform, chunk: IN, _, cb) {
86
+ // Stop processing if isSettled
87
+ if (isSettled) return cb()
81
88
 
82
- try {
83
- if (!predicate(chunk, ++index)) {
84
- cb() // signal that we've finished processing, but emit no output here
85
- return
86
- }
89
+ const currentIndex = ++index
87
90
 
91
+ try {
88
92
  // map and pass through
89
- const v = mapper(chunk, index)
93
+ const v = mapper(chunk, currentIndex)
94
+
95
+ const passedResults = (flattenArrayOutput && Array.isArray(v) ? v : [v]).filter(r => {
96
+ if (r === END) {
97
+ isSettled = true // will be checked later
98
+ return false
99
+ }
100
+ return r !== SKIP && (!predicate || predicate(r, currentIndex))
101
+ })
90
102
 
91
- if (flattenArrayOutput && Array.isArray(v)) {
92
- // Pass each item individually
93
- v.forEach(item => this.push(item))
94
- } else {
95
- cb(null, v)
103
+ passedResults.forEach(r => this.push(r))
104
+
105
+ if (isSettled) {
106
+ logger.log(`transformMapSync END received at index ${currentIndex}`)
107
+ pipelineClose('transformMapSync', this, this.sourceReadable, this.streamDone, logger)
96
108
  }
109
+
110
+ cb() // done processing
97
111
  } catch (err) {
98
112
  logger.error(err)
99
113
  errors++
@@ -107,7 +121,7 @@ export function transformMapSync<IN = any, OUT = IN>(
107
121
  }
108
122
 
109
123
  if (errorMode === ErrorMode.THROW_IMMEDIATELY) {
110
- isRejected = true
124
+ isSettled = true
111
125
  // Emit error immediately
112
126
  return cb(err as Error)
113
127
  }
@@ -9,7 +9,7 @@ import { TransformTyped } from '../stream.model'
9
9
  export function transformNoOp<T = any>(): TransformTyped<T, T> {
10
10
  return new Transform({
11
11
  objectMode: true,
12
- transform(chunk: T, _encoding, cb) {
12
+ transform(chunk: T, _, cb) {
13
13
  cb(null, chunk)
14
14
  },
15
15
  })
@@ -17,16 +17,16 @@ export function transformTap<IN>(
17
17
  opt: TransformTapOptions = {},
18
18
  ): TransformTyped<IN, IN> {
19
19
  const { logger = console } = opt
20
- let index = 0
20
+ let index = -1
21
21
 
22
22
  return new Transform({
23
23
  objectMode: true,
24
24
  ...opt,
25
- async transform(chunk: IN, _encoding, cb) {
25
+ async transform(chunk: IN, _, cb) {
26
26
  // console.log('tap', chunk)
27
27
 
28
28
  try {
29
- await fn(chunk, index++)
29
+ await fn(chunk, ++index)
30
30
  } catch (err) {
31
31
  logger.error(err)
32
32
  // suppressed error
@@ -10,7 +10,7 @@ export function transformToArray<IN>(opt: TransformOptions = {}): TransformTyped
10
10
  return new Transform({
11
11
  objectMode: true,
12
12
  ...opt,
13
- transform(chunk: IN, _encoding, cb) {
13
+ transform(chunk: IN, _, cb) {
14
14
  res.push(chunk)
15
15
  // callback to signal that we processed input, but not emitting any output
16
16
  cb()
@@ -14,8 +14,8 @@ export function transformToString(): TransformTyped<Buffer, string> {
14
14
  return new Transform({
15
15
  objectMode: false,
16
16
  readableObjectMode: true,
17
- transform(chunk: Buffer, _encoding, cb) {
18
- // console.log(`enc: ${_encoding}`, chunk.toString())
17
+ transform(chunk: Buffer, _, cb) {
18
+ // console.log(`enc: ${_}`, chunk.toString())
19
19
  cb(null, chunk.toString())
20
20
  },
21
21
  })
@@ -115,7 +115,7 @@ export function transformMultiThreaded<IN, OUT>(
115
115
  }
116
116
  },
117
117
  },
118
- async function transformMapFn(chunk: IN, _encoding, cb) {
118
+ async function transformMapFn(chunk: IN, _, cb) {
119
119
  // Freezing the index, because it may change due to concurrency
120
120
  const currentIndex = ++index
121
121
 
@@ -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
@@ -31,7 +31,7 @@ export function writableFork<T>(
31
31
  return new Writable({
32
32
  objectMode: true,
33
33
  ...opt,
34
- write(chunk: T, _encoding, cb) {
34
+ write(chunk: T, _, cb) {
35
35
  // Push/fork to all sub-streams
36
36
  // No backpressure is ensured here, it'll push regardless of the
37
37
  readables.forEach(readable => readable.push(chunk))
@@ -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
+ }
@@ -8,7 +8,7 @@ export function writablePushToArray<IN>(arr: IN[], opt: TransformOptions = {}):
8
8
  return new Writable({
9
9
  objectMode: true,
10
10
  ...opt,
11
- write(chunk: IN, _encoding, cb) {
11
+ write(chunk: IN, _, cb) {
12
12
  arr.push(chunk)
13
13
  // callback to signal that we processed input, but not emitting any output
14
14
  cb()
@@ -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
- write(chunk, _encoding, cb) {
21
+ write(chunk, _, cb) {
22
+ cb()
23
+ },
24
+ final(cb) {
14
25
  cb()
26
+ opt.streamDone?.resolve()
15
27
  },
16
28
  })
17
29
  }