@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.
- package/dist/index.d.ts +19 -18
- package/dist/index.js +19 -92
- package/dist/log/log.util.d.ts +4 -0
- package/dist/log/log.util.js +11 -0
- package/dist/stream/ndjson/ndjsonMap.d.ts +2 -2
- package/dist/stream/ndjson/ndjsonMap.js +4 -3
- package/dist/stream/ndjson/ndjsonStreamForEach.d.ts +2 -2
- package/dist/stream/pipeline/pipeline.d.ts +25 -3
- package/dist/stream/pipeline/pipeline.js +76 -9
- package/dist/stream/readable/readableCreate.d.ts +8 -0
- package/dist/stream/readable/readableCreate.js +9 -1
- package/dist/stream/readable/readableForEach.d.ts +2 -2
- package/dist/stream/readable/readableFromArray.d.ts +2 -2
- package/dist/stream/readable/readableFromArray.js +17 -13
- package/dist/stream/readable/readableMap.d.ts +2 -2
- package/dist/stream/readable/readableMap.js +22 -17
- package/dist/stream/stream.util.d.ts +4 -0
- package/dist/stream/stream.util.js +24 -0
- package/dist/stream/transform/transformLimit.d.ts +36 -1
- package/dist/stream/transform/transformLimit.js +33 -15
- package/dist/stream/transform/transformLogProgress.d.ts +2 -1
- package/dist/stream/transform/transformLogProgress.js +4 -4
- package/dist/stream/transform/transformMap.d.ts +2 -6
- package/dist/stream/transform/transformMap.js +51 -53
- 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/transform/worker/workerClassProxy.js +1 -0
- package/dist/stream/writable/writableFork.d.ts +2 -0
- package/dist/stream/writable/writableFork.js +2 -0
- package/dist/stream/writable/writableLimit.d.ts +9 -0
- package/dist/stream/writable/writableLimit.js +29 -0
- package/dist/stream/writable/writableVoid.d.ts +8 -1
- package/dist/stream/writable/writableVoid.js +5 -1
- package/package.json +1 -1
- package/src/index.ts +17 -156
- package/src/log/log.util.ts +9 -0
- package/src/stream/ndjson/ndjsonMap.ts +7 -5
- package/src/stream/ndjson/ndjsonStreamForEach.ts +2 -2
- package/src/stream/pipeline/pipeline.ts +102 -9
- package/src/stream/readable/readableCreate.ts +9 -1
- package/src/stream/readable/readableForEach.ts +2 -2
- package/src/stream/readable/readableFromArray.ts +18 -21
- package/src/stream/readable/readableMap.ts +24 -21
- package/src/stream/stream.util.ts +29 -0
- package/src/stream/transform/transformLimit.ts +71 -19
- package/src/stream/transform/transformLogProgress.ts +7 -4
- package/src/stream/transform/transformMap.ts +74 -82
- 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/transform/worker/workerClassProxy.js +1 -0
- package/src/stream/writable/writableFork.ts +2 -0
- package/src/stream/writable/writableLimit.ts +28 -0
- package/src/stream/writable/writableVoid.ts +13 -1
- package/dist/stream/transform/legacy/transformMap.d.ts +0 -17
- package/dist/stream/transform/legacy/transformMap.js +0 -94
- package/src/stream/transform/legacy/transformMap.ts +0 -133
|
@@ -1,32 +1,84 @@
|
|
|
1
|
-
import {
|
|
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
|
|
9
|
-
|
|
10
|
-
)
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
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:
|
|
18
|
-
|
|
59
|
+
transform(this: TransformLimit, chunk, _, cb) {
|
|
60
|
+
i++
|
|
19
61
|
|
|
20
|
-
if (
|
|
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
|
-
|
|
29
|
-
|
|
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
|
-
|
|
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)}`,
|
|
@@ -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
|
-
|
|
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:
|
|
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
|
|
97
|
+
let isSettled = false
|
|
98
98
|
let errors = 0
|
|
99
99
|
const collectedErrors: Error[] = [] // only used if errorMode == THROW_AGGREGATED
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
111
|
-
// console.log('transformMap final', {index}, q.inFlight, q.queueSize)
|
|
107
|
+
logErrorStats(true)
|
|
112
108
|
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
146
|
+
cb() // done processing
|
|
147
|
+
} catch (err) {
|
|
148
|
+
logger.error(err)
|
|
149
|
+
errors++
|
|
150
|
+
logErrorStats()
|
|
117
151
|
|
|
118
|
-
|
|
152
|
+
if (onError) {
|
|
153
|
+
try {
|
|
154
|
+
onError(err, chunk)
|
|
155
|
+
} catch {}
|
|
156
|
+
}
|
|
119
157
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
130
|
-
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
|
@@ -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
|
|
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;
|