@naturalcycles/nodejs-lib 12.55.0 → 12.57.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.
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -3
- package/dist/stream/ndjson/ndJsonFileRead.js +1 -1
- package/dist/stream/transform/{beta/transformMap2.d.ts → legacy/transformMap.d.ts} +1 -1
- package/dist/stream/transform/legacy/transformMap.js +94 -0
- package/dist/stream/transform/transformLimit.d.ts +5 -1
- package/dist/stream/transform/transformLimit.js +2 -1
- package/dist/stream/transform/transformLogProgress.d.ts +2 -1
- package/dist/stream/transform/transformLogProgress.js +4 -4
- package/dist/stream/transform/transformMap.js +54 -45
- package/dist/stream/transform/transformMapSimple.d.ts +2 -1
- package/dist/stream/transform/transformMapSimple.js +2 -2
- package/dist/stream/transform/transformMapSync.d.ts +2 -1
- package/dist/stream/transform/transformMapSync.js +3 -3
- package/dist/stream/transform/transformTap.d.ts +5 -2
- package/dist/stream/transform/transformTap.js +2 -1
- package/dist/stream/writable/writableForEach.d.ts +5 -1
- package/dist/stream/writable/writableForEach.js +8 -1
- package/dist/stream/writable/writableFork.d.ts +2 -0
- package/dist/stream/writable/writableFork.js +2 -0
- package/package.json +1 -1
- package/src/index.ts +1 -2
- package/src/stream/ndjson/ndJsonFileRead.ts +2 -2
- package/src/stream/transform/legacy/transformMap.ts +133 -0
- package/src/stream/transform/transformLimit.ts +8 -2
- package/src/stream/transform/transformLogProgress.ts +7 -4
- package/src/stream/transform/transformMap.ts +72 -66
- package/src/stream/transform/transformMapSimple.ts +5 -3
- package/src/stream/transform/transformMapSync.ts +6 -3
- package/src/stream/transform/transformTap.ts +8 -3
- package/src/stream/writable/writableForEach.ts +12 -2
- package/src/stream/writable/writableFork.ts +2 -0
- package/dist/stream/transform/beta/transformMap2.js +0 -104
- package/src/stream/transform/beta/transformMap2.ts +0 -142
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { Transform } from 'stream'
|
|
2
|
+
import {
|
|
3
|
+
AggregatedError,
|
|
4
|
+
AsyncMapper,
|
|
5
|
+
CommonLogger,
|
|
6
|
+
ErrorMode,
|
|
7
|
+
pFilter,
|
|
8
|
+
} from '@naturalcycles/js-lib'
|
|
9
|
+
import through2Concurrent = require('through2-concurrent')
|
|
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 transformMapLegacy<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
|
+
return through2Concurrent.obj(
|
|
51
|
+
{
|
|
52
|
+
maxConcurrency: concurrency,
|
|
53
|
+
// autoDestroy: true,
|
|
54
|
+
async final(cb) {
|
|
55
|
+
// console.log('transformMap final')
|
|
56
|
+
|
|
57
|
+
logErrorStats(logger, true)
|
|
58
|
+
|
|
59
|
+
await beforeFinal?.() // call beforeFinal if defined
|
|
60
|
+
|
|
61
|
+
if (collectedErrors.length) {
|
|
62
|
+
// emit Aggregated error
|
|
63
|
+
cb(new AggregatedError(collectedErrors))
|
|
64
|
+
} else {
|
|
65
|
+
// emit no error
|
|
66
|
+
cb()
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
async function transformMapFn(
|
|
71
|
+
this: Transform,
|
|
72
|
+
chunk: IN,
|
|
73
|
+
_encoding: any,
|
|
74
|
+
cb: (...args: any[]) => any,
|
|
75
|
+
) {
|
|
76
|
+
index++
|
|
77
|
+
// console.log({chunk, _encoding})
|
|
78
|
+
|
|
79
|
+
// Stop processing if THROW_IMMEDIATELY mode is used
|
|
80
|
+
if (isRejected && errorMode === ErrorMode.THROW_IMMEDIATELY) return cb()
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
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)
|
|
84
|
+
const res = await mapper(chunk, currentIndex)
|
|
85
|
+
const passedResults = await pFilter(
|
|
86
|
+
flattenArrayOutput && Array.isArray(res) ? res : [res],
|
|
87
|
+
async r => await predicate(r, currentIndex),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if (passedResults.length === 0) {
|
|
91
|
+
cb() // 0 results
|
|
92
|
+
} else {
|
|
93
|
+
passedResults.forEach(r => {
|
|
94
|
+
this.push(r)
|
|
95
|
+
// cb(null, r)
|
|
96
|
+
})
|
|
97
|
+
cb() // done processing
|
|
98
|
+
}
|
|
99
|
+
} catch (err) {
|
|
100
|
+
logger.error(err)
|
|
101
|
+
|
|
102
|
+
errors++
|
|
103
|
+
|
|
104
|
+
logErrorStats(logger)
|
|
105
|
+
|
|
106
|
+
if (onError) {
|
|
107
|
+
try {
|
|
108
|
+
onError(err, chunk)
|
|
109
|
+
} catch {}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (errorMode === ErrorMode.THROW_IMMEDIATELY) {
|
|
113
|
+
isRejected = true
|
|
114
|
+
// Emit error immediately
|
|
115
|
+
return cb(err)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (errorMode === ErrorMode.THROW_AGGREGATED) {
|
|
119
|
+
collectedErrors.push(err as Error)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Tell input stream that we're done processing, but emit nothing to output - not error nor result
|
|
123
|
+
cb()
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
function logErrorStats(logger: CommonLogger, final = false): void {
|
|
129
|
+
if (!errors) return
|
|
130
|
+
|
|
131
|
+
logger.log(`${metric} ${final ? 'final ' : ''}errors: ${yellow(errors)}`)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import { Transform } from 'stream'
|
|
2
|
+
import { CommonLogger } from '@naturalcycles/js-lib'
|
|
2
3
|
import { TransformOptions, TransformTyped } from '../stream.model'
|
|
3
4
|
|
|
5
|
+
export interface TransformLimitOptions extends TransformOptions {
|
|
6
|
+
logger?: CommonLogger
|
|
7
|
+
}
|
|
8
|
+
|
|
4
9
|
/**
|
|
5
10
|
* 0 or falsy value means "no limit"
|
|
6
11
|
*/
|
|
7
12
|
export function transformLimit<IN>(
|
|
8
13
|
limit?: number,
|
|
9
|
-
opt:
|
|
14
|
+
opt: TransformLimitOptions = {},
|
|
10
15
|
): TransformTyped<IN, IN> {
|
|
16
|
+
const { logger = console } = opt
|
|
11
17
|
let index = 0
|
|
12
18
|
let ended = false
|
|
13
19
|
|
|
@@ -25,7 +31,7 @@ export function transformLimit<IN>(
|
|
|
25
31
|
|
|
26
32
|
if (limit && index === limit) {
|
|
27
33
|
ended = true
|
|
28
|
-
|
|
34
|
+
logger.log(`transformLimit: limit of ${limit} reached`)
|
|
29
35
|
// this.emit('end') // this makes it "halt" on Node 14 lts
|
|
30
36
|
}
|
|
31
37
|
},
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)}`,
|
|
@@ -6,8 +6,8 @@ import {
|
|
|
6
6
|
CommonLogger,
|
|
7
7
|
ErrorMode,
|
|
8
8
|
pFilter,
|
|
9
|
+
PQueue,
|
|
9
10
|
} from '@naturalcycles/js-lib'
|
|
10
|
-
import through2Concurrent = require('through2-concurrent')
|
|
11
11
|
import { yellow } from '../../colors'
|
|
12
12
|
import { TransformTyped } from '../stream.model'
|
|
13
13
|
|
|
@@ -98,83 +98,89 @@ export function transformMap<IN = any, OUT = IN>(
|
|
|
98
98
|
let errors = 0
|
|
99
99
|
const collectedErrors: Error[] = [] // only used if errorMode == THROW_AGGREGATED
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
// console.log('transformMap final')
|
|
101
|
+
const q = new PQueue({
|
|
102
|
+
concurrency,
|
|
103
|
+
resolveOn: 'start',
|
|
104
|
+
// debug: true,
|
|
105
|
+
})
|
|
107
106
|
|
|
108
|
-
|
|
107
|
+
return new Transform({
|
|
108
|
+
objectMode: true,
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
async final(cb) {
|
|
111
|
+
// console.log('transformMap final', {index}, q.inFlight, q.queueSize)
|
|
111
112
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
cb(new AggregatedError(collectedErrors))
|
|
115
|
-
} else {
|
|
116
|
-
// emit no error
|
|
117
|
-
cb()
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
async function transformMapFn(
|
|
122
|
-
this: Transform,
|
|
123
|
-
chunk: IN,
|
|
124
|
-
_encoding: any,
|
|
125
|
-
cb: (...args: any[]) => any,
|
|
126
|
-
) {
|
|
127
|
-
index++
|
|
128
|
-
// console.log({chunk, _encoding})
|
|
129
|
-
|
|
130
|
-
// Stop processing if THROW_IMMEDIATELY mode is used
|
|
131
|
-
if (isRejected && errorMode === ErrorMode.THROW_IMMEDIATELY) return cb()
|
|
113
|
+
// wait for the current inFlight jobs to complete and push their results
|
|
114
|
+
await q.onIdle()
|
|
132
115
|
|
|
133
|
-
|
|
134
|
-
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)
|
|
135
|
-
const res = await mapper(chunk, currentIndex)
|
|
136
|
-
const passedResults = await pFilter(
|
|
137
|
-
flattenArrayOutput && Array.isArray(res) ? res : [res],
|
|
138
|
-
async r => await predicate(r, currentIndex),
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
if (passedResults.length === 0) {
|
|
142
|
-
cb() // 0 results
|
|
143
|
-
} else {
|
|
144
|
-
passedResults.forEach(r => {
|
|
145
|
-
this.push(r)
|
|
146
|
-
// cb(null, r)
|
|
147
|
-
})
|
|
148
|
-
cb() // done processing
|
|
149
|
-
}
|
|
150
|
-
} catch (err) {
|
|
151
|
-
logger.error(err)
|
|
116
|
+
logErrorStats(logger, true)
|
|
152
117
|
|
|
153
|
-
|
|
118
|
+
await beforeFinal?.() // call beforeFinal if defined
|
|
154
119
|
|
|
155
|
-
|
|
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
|
+
},
|
|
156
133
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
} catch {}
|
|
161
|
-
}
|
|
134
|
+
async transform(this: Transform, chunk: IN, _encoding, cb) {
|
|
135
|
+
index++
|
|
136
|
+
// console.log('transform', {index})
|
|
162
137
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
// Emit error immediately
|
|
166
|
-
return cb(err)
|
|
167
|
-
}
|
|
138
|
+
// Stop processing if THROW_IMMEDIATELY mode is used
|
|
139
|
+
if (isRejected && errorMode === ErrorMode.THROW_IMMEDIATELY) return cb()
|
|
168
140
|
|
|
169
|
-
|
|
170
|
-
|
|
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
|
+
}
|
|
171
176
|
}
|
|
177
|
+
})
|
|
172
178
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
179
|
+
// Resolved, which means it STARTED processing
|
|
180
|
+
// This means we can take more load
|
|
181
|
+
cb()
|
|
176
182
|
},
|
|
177
|
-
)
|
|
183
|
+
})
|
|
178
184
|
|
|
179
185
|
function logErrorStats(logger: CommonLogger, final = false): void {
|
|
180
186
|
if (!errors) return
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
31
|
+
logger.error(err)
|
|
27
32
|
// suppressed error
|
|
28
33
|
}
|
|
29
34
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AsyncMapper, _passNothingPredicate } from '@naturalcycles/js-lib'
|
|
2
|
-
import { transformMap, TransformMapOptions } from '../..'
|
|
1
|
+
import { AsyncMapper, _passNothingPredicate, Mapper } from '@naturalcycles/js-lib'
|
|
2
|
+
import { transformMap, TransformMapOptions, transformMapSync } from '../..'
|
|
3
3
|
import { WritableTyped } from '../stream.model'
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -11,3 +11,13 @@ export function writableForEach<IN = any>(
|
|
|
11
11
|
): WritableTyped<IN> {
|
|
12
12
|
return transformMap<IN, void>(mapper, { ...opt, predicate: _passNothingPredicate })
|
|
13
13
|
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Just an alias to transformMap that declares OUT as void.
|
|
17
|
+
*/
|
|
18
|
+
export function writableForEachSync<IN = any>(
|
|
19
|
+
mapper: Mapper<IN, void>,
|
|
20
|
+
opt: TransformMapOptions<IN, void> = {},
|
|
21
|
+
): WritableTyped<IN> {
|
|
22
|
+
return transformMapSync<IN, void>(mapper, { ...opt, predicate: _passNothingPredicate })
|
|
23
|
+
}
|
|
@@ -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[][],
|
|
@@ -1,104 +0,0 @@
|
|
|
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;
|