@naturalcycles/nodejs-lib 15.27.3 → 15.28.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/stream/index.d.ts +1 -1
- package/dist/stream/index.js +1 -1
- package/dist/stream/pipeline.d.ts +9 -3
- package/dist/stream/pipeline.js +14 -1
- package/dist/stream/readable/{readableCreate.d.ts → createReadable.d.ts} +9 -3
- package/dist/stream/readable/{readableCreate.js → createReadable.js} +23 -2
- package/dist/stream/stream.model.d.ts +4 -14
- package/dist/stream/transform/transformFork.js +4 -4
- package/dist/stream/transform/transformMap.d.ts +2 -2
- package/dist/stream/transform/transformTap.d.ts +6 -6
- package/dist/stream/transform/transformTap.js +24 -4
- package/package.json +2 -2
- package/src/stream/index.ts +1 -1
- package/src/stream/pipeline.ts +18 -3
- package/src/stream/readable/{readableCreate.ts → createReadable.ts} +26 -3
- package/src/stream/stream.model.ts +7 -15
- package/src/stream/transform/transformFork.ts +4 -4
- package/src/stream/transform/transformMap.ts +2 -1
- package/src/stream/transform/transformTap.ts +31 -12
package/dist/stream/index.d.ts
CHANGED
|
@@ -5,8 +5,8 @@ export * from './ndjson/transformJsonParse.js';
|
|
|
5
5
|
export * from './ndjson/transformToNDJson.js';
|
|
6
6
|
export * from './pipeline.js';
|
|
7
7
|
export * from './progressLogger.js';
|
|
8
|
+
export * from './readable/createReadable.js';
|
|
8
9
|
export * from './readable/readableCombined.js';
|
|
9
|
-
export * from './readable/readableCreate.js';
|
|
10
10
|
export * from './readable/readableFromArray.js';
|
|
11
11
|
export * from './stream.model.js';
|
|
12
12
|
export * from './transform/transformChunk.js';
|
package/dist/stream/index.js
CHANGED
|
@@ -5,8 +5,8 @@ export * from './ndjson/transformJsonParse.js';
|
|
|
5
5
|
export * from './ndjson/transformToNDJson.js';
|
|
6
6
|
export * from './pipeline.js';
|
|
7
7
|
export * from './progressLogger.js';
|
|
8
|
+
export * from './readable/createReadable.js';
|
|
8
9
|
export * from './readable/readableCombined.js';
|
|
9
|
-
export * from './readable/readableCreate.js';
|
|
10
10
|
export * from './readable/readableFromArray.js';
|
|
11
11
|
export * from './stream.model.js';
|
|
12
12
|
export * from './transform/transformChunk.js';
|
|
@@ -8,9 +8,8 @@ import { type TransformMapOptions } from './transform/transformMap.js';
|
|
|
8
8
|
import { type TransformMapSimpleOptions } from './transform/transformMapSimple.js';
|
|
9
9
|
import { type TransformMapSyncOptions } from './transform/transformMapSync.js';
|
|
10
10
|
import { type TransformOffsetOptions } from './transform/transformOffset.js';
|
|
11
|
-
import { type TransformTapOptions } from './transform/transformTap.js';
|
|
12
11
|
import { type TransformThrottleOptions } from './transform/transformThrottle.js';
|
|
13
|
-
export declare class Pipeline<T> {
|
|
12
|
+
export declare class Pipeline<T = unknown> {
|
|
14
13
|
private readonly source;
|
|
15
14
|
private transforms;
|
|
16
15
|
private destination?;
|
|
@@ -19,6 +18,12 @@ export declare class Pipeline<T> {
|
|
|
19
18
|
private abortableSignal;
|
|
20
19
|
private constructor();
|
|
21
20
|
static from<T>(source: ReadableTyped<T>): Pipeline<T>;
|
|
21
|
+
/**
|
|
22
|
+
* Useful in cases when Readable is not immediately available,
|
|
23
|
+
* but only available after an async operation is completed.
|
|
24
|
+
* Implemented via a proxy Transform, which should be transparent.
|
|
25
|
+
*/
|
|
26
|
+
static fromAsyncReadable<T = unknown>(fn: () => Promise<ReadableTyped<T>>): Pipeline<T>;
|
|
22
27
|
static fromWeb<T>(webReadableStream: WebReadableStream<T>): Pipeline<T>;
|
|
23
28
|
/**
|
|
24
29
|
* Technically same as `fromIterable` (since Array is Iterable),
|
|
@@ -61,7 +66,8 @@ export declare class Pipeline<T> {
|
|
|
61
66
|
filter(predicate: AsyncPredicate<T>, opt?: TransformMapOptions): this;
|
|
62
67
|
filterSync(predicate: Predicate<T>, opt?: TransformOptions): this;
|
|
63
68
|
offset(opt: TransformOffsetOptions): this;
|
|
64
|
-
tap(fn: AsyncIndexedMapper<T, any>, opt?:
|
|
69
|
+
tap(fn: AsyncIndexedMapper<T, any>, opt?: TransformOptions): this;
|
|
70
|
+
tapSync(fn: IndexedMapper<T, any>, opt?: TransformOptions): this;
|
|
65
71
|
throttle(opt: TransformThrottleOptions): this;
|
|
66
72
|
transform<TO>(transform: TransformTyped<T, TO>): Pipeline<TO>;
|
|
67
73
|
/**
|
package/dist/stream/pipeline.js
CHANGED
|
@@ -7,6 +7,7 @@ import { fs2 } from '../fs/fs2.js';
|
|
|
7
7
|
import { createReadStreamAsNDJson } from './ndjson/createReadStreamAsNDJson.js';
|
|
8
8
|
import { transformJsonParse } from './ndjson/transformJsonParse.js';
|
|
9
9
|
import { transformToNDJson } from './ndjson/transformToNDJson.js';
|
|
10
|
+
import { createReadableFromAsync } from './readable/createReadable.js';
|
|
10
11
|
import { PIPELINE_GRACEFUL_ABORT } from './stream.util.js';
|
|
11
12
|
import { transformChunk } from './transform/transformChunk.js';
|
|
12
13
|
import { transformFilterSync } from './transform/transformFilter.js';
|
|
@@ -19,7 +20,7 @@ import { transformMapSimple, } from './transform/transformMapSimple.js';
|
|
|
19
20
|
import { transformMapSync } from './transform/transformMapSync.js';
|
|
20
21
|
import { transformOffset } from './transform/transformOffset.js';
|
|
21
22
|
import { transformSplitOnNewline } from './transform/transformSplit.js';
|
|
22
|
-
import { transformTap } from './transform/transformTap.js';
|
|
23
|
+
import { transformTap, transformTapSync } from './transform/transformTap.js';
|
|
23
24
|
import { transformThrottle } from './transform/transformThrottle.js';
|
|
24
25
|
import { writablePushToArray } from './writable/writablePushToArray.js';
|
|
25
26
|
import { writableVoid } from './writable/writableVoid.js';
|
|
@@ -39,6 +40,14 @@ export class Pipeline {
|
|
|
39
40
|
static from(source) {
|
|
40
41
|
return new Pipeline(source);
|
|
41
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Useful in cases when Readable is not immediately available,
|
|
45
|
+
* but only available after an async operation is completed.
|
|
46
|
+
* Implemented via a proxy Transform, which should be transparent.
|
|
47
|
+
*/
|
|
48
|
+
static fromAsyncReadable(fn) {
|
|
49
|
+
return new Pipeline(createReadableFromAsync(fn));
|
|
50
|
+
}
|
|
42
51
|
static fromWeb(webReadableStream) {
|
|
43
52
|
return new Pipeline(Readable.fromWeb(webReadableStream));
|
|
44
53
|
}
|
|
@@ -152,6 +161,10 @@ export class Pipeline {
|
|
|
152
161
|
this.transforms.push(transformTap(fn, opt));
|
|
153
162
|
return this;
|
|
154
163
|
}
|
|
164
|
+
tapSync(fn, opt) {
|
|
165
|
+
this.transforms.push(transformTapSync(fn, opt));
|
|
166
|
+
return this;
|
|
167
|
+
}
|
|
155
168
|
throttle(opt) {
|
|
156
169
|
this.transforms.push(transformThrottle(opt));
|
|
157
170
|
return this;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ReadableOptions } from 'node:stream';
|
|
2
2
|
import type { ReadableTyped } from '../stream.model.js';
|
|
3
3
|
/**
|
|
4
4
|
* Convenience function to create a Readable that can be pushed into (similar to RxJS Subject).
|
|
@@ -12,8 +12,14 @@ import type { ReadableTyped } from '../stream.model.js';
|
|
|
12
12
|
* if read() will be called AFTER everything was pushed and Readable is closed (by pushing `null`).
|
|
13
13
|
* Beware of it when e.g doing unit testing! Jest prefers to hang (not exit-0).
|
|
14
14
|
*/
|
|
15
|
-
export declare function
|
|
15
|
+
export declare function createReadable<T>(items?: Iterable<T>, opt?: ReadableOptions, onRead?: () => void): ReadableTyped<T>;
|
|
16
16
|
/**
|
|
17
17
|
* Convenience type-safe wrapper around Readable.from() that infers the Type of input.
|
|
18
18
|
*/
|
|
19
|
-
export declare function
|
|
19
|
+
export declare function createReadableFrom<T>(iterable: Iterable<T> | AsyncIterable<T>, opt?: ReadableOptions): ReadableTyped<T>;
|
|
20
|
+
/**
|
|
21
|
+
* Allows to "create Readable asynchronously".
|
|
22
|
+
* Implemented via a proxy Transform, which is created (and returned) eagerly,
|
|
23
|
+
* and later (when source Readable is created) serves as a pass-through proxy.
|
|
24
|
+
*/
|
|
25
|
+
export declare function createReadableFromAsync<T>(fn: () => Promise<ReadableTyped<T>>): ReadableTyped<T>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Transform } from 'node:stream';
|
|
1
2
|
import { Readable } from 'node:stream';
|
|
2
3
|
/**
|
|
3
4
|
* Convenience function to create a Readable that can be pushed into (similar to RxJS Subject).
|
|
@@ -11,7 +12,7 @@ import { Readable } from 'node:stream';
|
|
|
11
12
|
* if read() will be called AFTER everything was pushed and Readable is closed (by pushing `null`).
|
|
12
13
|
* Beware of it when e.g doing unit testing! Jest prefers to hang (not exit-0).
|
|
13
14
|
*/
|
|
14
|
-
export function
|
|
15
|
+
export function createReadable(items = [], opt, onRead) {
|
|
15
16
|
const readable = new Readable({
|
|
16
17
|
objectMode: true,
|
|
17
18
|
...opt,
|
|
@@ -27,6 +28,26 @@ export function readableCreate(items = [], opt, onRead) {
|
|
|
27
28
|
/**
|
|
28
29
|
* Convenience type-safe wrapper around Readable.from() that infers the Type of input.
|
|
29
30
|
*/
|
|
30
|
-
export function
|
|
31
|
+
export function createReadableFrom(iterable, opt) {
|
|
31
32
|
return Readable.from(iterable, opt);
|
|
32
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Allows to "create Readable asynchronously".
|
|
36
|
+
* Implemented via a proxy Transform, which is created (and returned) eagerly,
|
|
37
|
+
* and later (when source Readable is created) serves as a pass-through proxy.
|
|
38
|
+
*/
|
|
39
|
+
export function createReadableFromAsync(fn) {
|
|
40
|
+
const transform = new Transform({
|
|
41
|
+
objectMode: true,
|
|
42
|
+
highWaterMark: 1,
|
|
43
|
+
transform: (chunk, _encoding, cb) => {
|
|
44
|
+
cb(null, chunk);
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
void fn()
|
|
48
|
+
.then(readable => {
|
|
49
|
+
readable.on('error', err => transform.destroy(err)).pipe(transform);
|
|
50
|
+
})
|
|
51
|
+
.catch(err => transform.destroy(err));
|
|
52
|
+
return transform;
|
|
53
|
+
}
|
|
@@ -11,11 +11,11 @@ export interface ReadableArrayOptions {
|
|
|
11
11
|
/** allows destroying the stream if the signal is aborted. */
|
|
12
12
|
signal?: AbortSignal;
|
|
13
13
|
}
|
|
14
|
-
export type ReadableMapper<IN, OUT> = (data: IN, opt?: ReadableSignalOptions) => Promisable<OUT>;
|
|
15
|
-
export type ReadableFlatMapper<IN, OUT> = (data: IN, opt?: ReadableSignalOptions) => Promisable<OUT[]>;
|
|
14
|
+
export type ReadableMapper<IN, OUT = unknown> = (data: IN, opt?: ReadableSignalOptions) => Promisable<OUT>;
|
|
15
|
+
export type ReadableFlatMapper<IN, OUT = unknown> = (data: IN, opt?: ReadableSignalOptions) => Promisable<OUT[]>;
|
|
16
16
|
export type ReadableVoidMapper<IN> = (data: IN, opt?: ReadableSignalOptions) => void | Promise<void>;
|
|
17
17
|
export type ReadablePredicate<IN> = (data: IN, opt?: ReadableSignalOptions) => boolean | Promise<boolean>;
|
|
18
|
-
export interface ReadableTyped<T> extends Readable {
|
|
18
|
+
export interface ReadableTyped<T = unknown> extends Readable {
|
|
19
19
|
toArray: (opt?: ReadableSignalOptions) => Promise<T[]>;
|
|
20
20
|
map: <OUT>(mapper: ReadableMapper<T, OUT>, opt?: ReadableArrayOptions) => ReadableTyped<OUT>;
|
|
21
21
|
flatMap: <OUT>(mapper: ReadableFlatMapper<T, OUT>, opt?: ReadableArrayOptions) => ReadableTyped<OUT>;
|
|
@@ -26,17 +26,7 @@ export interface ReadableTyped<T> extends Readable {
|
|
|
26
26
|
}
|
|
27
27
|
export interface WritableTyped<T> extends Writable {
|
|
28
28
|
}
|
|
29
|
-
|
|
30
|
-
* Type alias that indicates that the Readable is not in objectMode,
|
|
31
|
-
* e.g returns a binary stream (like a gzip stream).
|
|
32
|
-
*/
|
|
33
|
-
export type ReadableBinary = Readable;
|
|
34
|
-
/**
|
|
35
|
-
* Type alias that indicates that the Writable is not in objectMode,
|
|
36
|
-
* e.g reads a binary stream (like a gzip stream).
|
|
37
|
-
*/
|
|
38
|
-
export type WritableBinary = Writable;
|
|
39
|
-
export interface TransformTyped<IN, OUT> extends Transform {
|
|
29
|
+
export interface TransformTyped<IN = unknown, OUT = unknown> extends Transform {
|
|
40
30
|
}
|
|
41
31
|
export interface TransformOptions {
|
|
42
32
|
/**
|
|
@@ -2,7 +2,7 @@ import { Transform } from 'node:stream';
|
|
|
2
2
|
import { createCommonLoggerAtLevel } from '@naturalcycles/js-lib/log';
|
|
3
3
|
import { pDefer } from '@naturalcycles/js-lib/promise/pDefer.js';
|
|
4
4
|
import { Pipeline } from '../pipeline.js';
|
|
5
|
-
import {
|
|
5
|
+
import { createReadable } from '../readable/createReadable.js';
|
|
6
6
|
/**
|
|
7
7
|
* Allows to "fork" away from the "main pipeline" into the "forked pipeline".
|
|
8
8
|
*
|
|
@@ -14,12 +14,12 @@ export function transformFork(fn, opt = {}) {
|
|
|
14
14
|
const { objectMode = true, highWaterMark } = opt;
|
|
15
15
|
const logger = createCommonLoggerAtLevel(opt.logger, opt.logLevel);
|
|
16
16
|
let lock;
|
|
17
|
-
const fork =
|
|
17
|
+
const fork = createReadable([], {}, () => {
|
|
18
18
|
// `_read` is called
|
|
19
19
|
if (!lock)
|
|
20
20
|
return;
|
|
21
21
|
// We had a lock - let's Resume
|
|
22
|
-
logger.
|
|
22
|
+
logger.debug(`TransformFork: resume`);
|
|
23
23
|
const lockCopy = lock;
|
|
24
24
|
lock = undefined;
|
|
25
25
|
lockCopy.resolve();
|
|
@@ -45,7 +45,7 @@ export function transformFork(fn, opt = {}) {
|
|
|
45
45
|
if (!shouldContinue && !lock) {
|
|
46
46
|
// Forked pipeline indicates that we should Pause
|
|
47
47
|
lock = pDefer();
|
|
48
|
-
logger.
|
|
48
|
+
logger.debug(`TransformFork: pause`);
|
|
49
49
|
}
|
|
50
50
|
// acknowledge that we've finished processing the input chunk
|
|
51
51
|
cb();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type AbortableSignal } from '@naturalcycles/js-lib';
|
|
2
2
|
import { ErrorMode } from '@naturalcycles/js-lib/error';
|
|
3
|
-
import { type AbortableAsyncMapper, type AsyncPredicate, END, type PositiveInteger, type Promisable, SKIP, type StringMap, type UnixTimestampMillis } from '@naturalcycles/js-lib/types';
|
|
3
|
+
import { type AbortableAsyncMapper, type AsyncPredicate, END, type PositiveInteger, type Predicate, type Promisable, SKIP, type StringMap, type UnixTimestampMillis } from '@naturalcycles/js-lib/types';
|
|
4
4
|
import type { TransformOptions, TransformTyped } from '../stream.model.js';
|
|
5
5
|
export interface TransformMapOptions<IN = any, OUT = IN> extends TransformOptions {
|
|
6
6
|
/**
|
|
@@ -10,7 +10,7 @@ export interface TransformMapOptions<IN = any, OUT = IN> extends TransformOption
|
|
|
10
10
|
* Defaults to "pass everything" (including null, undefined, etc).
|
|
11
11
|
* Simpler way to exclude certain cases is to return SKIP symbol from the mapper.
|
|
12
12
|
*/
|
|
13
|
-
predicate?: AsyncPredicate<OUT>;
|
|
13
|
+
predicate?: Predicate<OUT> | AsyncPredicate<OUT>;
|
|
14
14
|
/**
|
|
15
15
|
* Number of concurrently pending promises returned by `mapper`.
|
|
16
16
|
*
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { AsyncIndexedMapper } from '@naturalcycles/js-lib/types';
|
|
1
|
+
import type { AsyncIndexedMapper, IndexedMapper } from '@naturalcycles/js-lib/types';
|
|
3
2
|
import type { TransformOptions, TransformTyped } from '../stream.model.js';
|
|
4
|
-
export interface TransformTapOptions extends TransformOptions {
|
|
5
|
-
logger?: CommonLogger;
|
|
6
|
-
}
|
|
7
3
|
/**
|
|
8
4
|
* Similar to RxJS `tap` - allows to run a function for each stream item, without affecting the result.
|
|
9
5
|
* Item is passed through to the output.
|
|
10
6
|
*
|
|
11
7
|
* Can also act as a counter, since `index` is passed to `fn`
|
|
12
8
|
*/
|
|
13
|
-
export declare function transformTap<IN>(fn: AsyncIndexedMapper<IN, any>, opt?:
|
|
9
|
+
export declare function transformTap<IN>(fn: AsyncIndexedMapper<IN, any>, opt?: TransformOptions): TransformTyped<IN, IN>;
|
|
10
|
+
/**
|
|
11
|
+
* Sync version of transformTap
|
|
12
|
+
*/
|
|
13
|
+
export declare function transformTapSync<IN>(fn: IndexedMapper<IN, any>, opt?: TransformOptions): TransformTyped<IN, IN>;
|
|
@@ -6,13 +6,12 @@ import { Transform } from 'node:stream';
|
|
|
6
6
|
* Can also act as a counter, since `index` is passed to `fn`
|
|
7
7
|
*/
|
|
8
8
|
export function transformTap(fn, opt = {}) {
|
|
9
|
-
const { logger = console } = opt;
|
|
9
|
+
const { logger = console, highWaterMark = 1 } = opt;
|
|
10
10
|
let index = -1;
|
|
11
11
|
return new Transform({
|
|
12
12
|
objectMode: true,
|
|
13
|
-
|
|
13
|
+
highWaterMark,
|
|
14
14
|
async transform(chunk, _, cb) {
|
|
15
|
-
// console.log('tap', chunk)
|
|
16
15
|
try {
|
|
17
16
|
await fn(chunk, ++index);
|
|
18
17
|
}
|
|
@@ -20,7 +19,28 @@ export function transformTap(fn, opt = {}) {
|
|
|
20
19
|
logger.error(err);
|
|
21
20
|
// suppressed error
|
|
22
21
|
}
|
|
23
|
-
cb(null, chunk);
|
|
22
|
+
cb(null, chunk);
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Sync version of transformTap
|
|
28
|
+
*/
|
|
29
|
+
export function transformTapSync(fn, opt = {}) {
|
|
30
|
+
const { logger = console, highWaterMark = 1 } = opt;
|
|
31
|
+
let index = -1;
|
|
32
|
+
return new Transform({
|
|
33
|
+
objectMode: true,
|
|
34
|
+
highWaterMark,
|
|
35
|
+
transform(chunk, _, cb) {
|
|
36
|
+
try {
|
|
37
|
+
fn(chunk, ++index);
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
logger.error(err);
|
|
41
|
+
// suppressed error
|
|
42
|
+
}
|
|
43
|
+
cb(null, chunk);
|
|
24
44
|
},
|
|
25
45
|
});
|
|
26
46
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@naturalcycles/nodejs-lib",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "15.
|
|
4
|
+
"version": "15.28.0",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@naturalcycles/js-lib": "^15",
|
|
7
7
|
"@types/js-yaml": "^4",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@types/through2-concurrent": "^2",
|
|
26
|
-
"@naturalcycles/dev-lib": "
|
|
26
|
+
"@naturalcycles/dev-lib": "19.37.0"
|
|
27
27
|
},
|
|
28
28
|
"exports": {
|
|
29
29
|
".": "./dist/index.js",
|
package/src/stream/index.ts
CHANGED
|
@@ -5,8 +5,8 @@ export * from './ndjson/transformJsonParse.js'
|
|
|
5
5
|
export * from './ndjson/transformToNDJson.js'
|
|
6
6
|
export * from './pipeline.js'
|
|
7
7
|
export * from './progressLogger.js'
|
|
8
|
+
export * from './readable/createReadable.js'
|
|
8
9
|
export * from './readable/readableCombined.js'
|
|
9
|
-
export * from './readable/readableCreate.js'
|
|
10
10
|
export * from './readable/readableFromArray.js'
|
|
11
11
|
export * from './stream.model.js'
|
|
12
12
|
export * from './transform/transformChunk.js'
|
package/src/stream/pipeline.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { fs2 } from '../fs/fs2.js'
|
|
|
20
20
|
import { createReadStreamAsNDJson } from './ndjson/createReadStreamAsNDJson.js'
|
|
21
21
|
import { transformJsonParse } from './ndjson/transformJsonParse.js'
|
|
22
22
|
import { transformToNDJson } from './ndjson/transformToNDJson.js'
|
|
23
|
+
import { createReadableFromAsync } from './readable/createReadable.js'
|
|
23
24
|
import type {
|
|
24
25
|
ReadableTyped,
|
|
25
26
|
TransformOptions,
|
|
@@ -44,12 +45,12 @@ import {
|
|
|
44
45
|
import { transformMapSync, type TransformMapSyncOptions } from './transform/transformMapSync.js'
|
|
45
46
|
import { transformOffset, type TransformOffsetOptions } from './transform/transformOffset.js'
|
|
46
47
|
import { transformSplitOnNewline } from './transform/transformSplit.js'
|
|
47
|
-
import { transformTap,
|
|
48
|
+
import { transformTap, transformTapSync } from './transform/transformTap.js'
|
|
48
49
|
import { transformThrottle, type TransformThrottleOptions } from './transform/transformThrottle.js'
|
|
49
50
|
import { writablePushToArray } from './writable/writablePushToArray.js'
|
|
50
51
|
import { writableVoid } from './writable/writableVoid.js'
|
|
51
52
|
|
|
52
|
-
export class Pipeline<T> {
|
|
53
|
+
export class Pipeline<T = unknown> {
|
|
53
54
|
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: ok
|
|
54
55
|
private readonly source: Readable
|
|
55
56
|
private transforms: NodeJS.ReadWriteStream[] = []
|
|
@@ -68,6 +69,15 @@ export class Pipeline<T> {
|
|
|
68
69
|
return new Pipeline(source)
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Useful in cases when Readable is not immediately available,
|
|
74
|
+
* but only available after an async operation is completed.
|
|
75
|
+
* Implemented via a proxy Transform, which should be transparent.
|
|
76
|
+
*/
|
|
77
|
+
static fromAsyncReadable<T = unknown>(fn: () => Promise<ReadableTyped<T>>): Pipeline<T> {
|
|
78
|
+
return new Pipeline(createReadableFromAsync(fn))
|
|
79
|
+
}
|
|
80
|
+
|
|
71
81
|
static fromWeb<T>(webReadableStream: WebReadableStream<T>): Pipeline<T> {
|
|
72
82
|
return new Pipeline(Readable.fromWeb(webReadableStream))
|
|
73
83
|
}
|
|
@@ -211,11 +221,16 @@ export class Pipeline<T> {
|
|
|
211
221
|
return this
|
|
212
222
|
}
|
|
213
223
|
|
|
214
|
-
tap(fn: AsyncIndexedMapper<T, any>, opt?:
|
|
224
|
+
tap(fn: AsyncIndexedMapper<T, any>, opt?: TransformOptions): this {
|
|
215
225
|
this.transforms.push(transformTap(fn, opt))
|
|
216
226
|
return this
|
|
217
227
|
}
|
|
218
228
|
|
|
229
|
+
tapSync(fn: IndexedMapper<T, any>, opt?: TransformOptions): this {
|
|
230
|
+
this.transforms.push(transformTapSync(fn, opt))
|
|
231
|
+
return this
|
|
232
|
+
}
|
|
233
|
+
|
|
219
234
|
throttle(opt: TransformThrottleOptions): this {
|
|
220
235
|
this.transforms.push(transformThrottle(opt))
|
|
221
236
|
return this
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ReadableOptions, Transform } from 'node:stream'
|
|
2
2
|
import { Readable } from 'node:stream'
|
|
3
3
|
import type { ReadableTyped } from '../stream.model.js'
|
|
4
4
|
|
|
@@ -14,7 +14,7 @@ import type { ReadableTyped } from '../stream.model.js'
|
|
|
14
14
|
* if read() will be called AFTER everything was pushed and Readable is closed (by pushing `null`).
|
|
15
15
|
* Beware of it when e.g doing unit testing! Jest prefers to hang (not exit-0).
|
|
16
16
|
*/
|
|
17
|
-
export function
|
|
17
|
+
export function createReadable<T>(
|
|
18
18
|
items: Iterable<T> = [],
|
|
19
19
|
opt?: ReadableOptions,
|
|
20
20
|
onRead?: () => void, // read callback
|
|
@@ -35,9 +35,32 @@ export function readableCreate<T>(
|
|
|
35
35
|
/**
|
|
36
36
|
* Convenience type-safe wrapper around Readable.from() that infers the Type of input.
|
|
37
37
|
*/
|
|
38
|
-
export function
|
|
38
|
+
export function createReadableFrom<T>(
|
|
39
39
|
iterable: Iterable<T> | AsyncIterable<T>,
|
|
40
40
|
opt?: ReadableOptions,
|
|
41
41
|
): ReadableTyped<T> {
|
|
42
42
|
return Readable.from(iterable, opt)
|
|
43
43
|
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Allows to "create Readable asynchronously".
|
|
47
|
+
* Implemented via a proxy Transform, which is created (and returned) eagerly,
|
|
48
|
+
* and later (when source Readable is created) serves as a pass-through proxy.
|
|
49
|
+
*/
|
|
50
|
+
export function createReadableFromAsync<T>(fn: () => Promise<ReadableTyped<T>>): ReadableTyped<T> {
|
|
51
|
+
const transform = new Transform({
|
|
52
|
+
objectMode: true,
|
|
53
|
+
highWaterMark: 1,
|
|
54
|
+
transform: (chunk, _encoding, cb) => {
|
|
55
|
+
cb(null, chunk)
|
|
56
|
+
},
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
void fn()
|
|
60
|
+
.then(readable => {
|
|
61
|
+
readable.on('error', err => transform.destroy(err)).pipe(transform)
|
|
62
|
+
})
|
|
63
|
+
.catch(err => transform.destroy(err))
|
|
64
|
+
|
|
65
|
+
return transform
|
|
66
|
+
}
|
|
@@ -14,9 +14,12 @@ export interface ReadableArrayOptions {
|
|
|
14
14
|
signal?: AbortSignal
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export type ReadableMapper<IN, OUT> = (
|
|
17
|
+
export type ReadableMapper<IN, OUT = unknown> = (
|
|
18
|
+
data: IN,
|
|
19
|
+
opt?: ReadableSignalOptions,
|
|
20
|
+
) => Promisable<OUT>
|
|
18
21
|
|
|
19
|
-
export type ReadableFlatMapper<IN, OUT> = (
|
|
22
|
+
export type ReadableFlatMapper<IN, OUT = unknown> = (
|
|
20
23
|
data: IN,
|
|
21
24
|
opt?: ReadableSignalOptions,
|
|
22
25
|
) => Promisable<OUT[]>
|
|
@@ -28,7 +31,7 @@ export type ReadablePredicate<IN> = (
|
|
|
28
31
|
opt?: ReadableSignalOptions,
|
|
29
32
|
) => boolean | Promise<boolean>
|
|
30
33
|
|
|
31
|
-
export interface ReadableTyped<T> extends Readable {
|
|
34
|
+
export interface ReadableTyped<T = unknown> extends Readable {
|
|
32
35
|
toArray: (opt?: ReadableSignalOptions) => Promise<T[]>
|
|
33
36
|
|
|
34
37
|
map: <OUT>(mapper: ReadableMapper<T, OUT>, opt?: ReadableArrayOptions) => ReadableTyped<OUT>
|
|
@@ -49,19 +52,8 @@ export interface ReadableTyped<T> extends Readable {
|
|
|
49
52
|
// biome-ignore lint/correctness/noUnusedVariables: ok
|
|
50
53
|
export interface WritableTyped<T> extends Writable {}
|
|
51
54
|
|
|
52
|
-
/**
|
|
53
|
-
* Type alias that indicates that the Readable is not in objectMode,
|
|
54
|
-
* e.g returns a binary stream (like a gzip stream).
|
|
55
|
-
*/
|
|
56
|
-
export type ReadableBinary = Readable
|
|
57
|
-
/**
|
|
58
|
-
* Type alias that indicates that the Writable is not in objectMode,
|
|
59
|
-
* e.g reads a binary stream (like a gzip stream).
|
|
60
|
-
*/
|
|
61
|
-
export type WritableBinary = Writable
|
|
62
|
-
|
|
63
55
|
// biome-ignore lint/correctness/noUnusedVariables: ok
|
|
64
|
-
export interface TransformTyped<IN, OUT> extends Transform {}
|
|
56
|
+
export interface TransformTyped<IN = unknown, OUT = unknown> extends Transform {}
|
|
65
57
|
|
|
66
58
|
export interface TransformOptions {
|
|
67
59
|
/**
|
|
@@ -2,7 +2,7 @@ import { Transform } from 'node:stream'
|
|
|
2
2
|
import { createCommonLoggerAtLevel } from '@naturalcycles/js-lib/log'
|
|
3
3
|
import { type DeferredPromise, pDefer } from '@naturalcycles/js-lib/promise/pDefer.js'
|
|
4
4
|
import { Pipeline } from '../pipeline.js'
|
|
5
|
-
import {
|
|
5
|
+
import { createReadable } from '../readable/createReadable.js'
|
|
6
6
|
import type { TransformOptions, TransformTyped } from '../stream.model.js'
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -21,11 +21,11 @@ export function transformFork<T>(
|
|
|
21
21
|
|
|
22
22
|
let lock: DeferredPromise | undefined
|
|
23
23
|
|
|
24
|
-
const fork =
|
|
24
|
+
const fork = createReadable<T>([], {}, () => {
|
|
25
25
|
// `_read` is called
|
|
26
26
|
if (!lock) return
|
|
27
27
|
// We had a lock - let's Resume
|
|
28
|
-
logger.
|
|
28
|
+
logger.debug(`TransformFork: resume`)
|
|
29
29
|
const lockCopy = lock
|
|
30
30
|
lock = undefined
|
|
31
31
|
lockCopy.resolve()
|
|
@@ -54,7 +54,7 @@ export function transformFork<T>(
|
|
|
54
54
|
if (!shouldContinue && !lock) {
|
|
55
55
|
// Forked pipeline indicates that we should Pause
|
|
56
56
|
lock = pDefer()
|
|
57
|
-
logger.
|
|
57
|
+
logger.debug(`TransformFork: pause`)
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
// acknowledge that we've finished processing the input chunk
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type AsyncPredicate,
|
|
9
9
|
END,
|
|
10
10
|
type PositiveInteger,
|
|
11
|
+
type Predicate,
|
|
11
12
|
type Promisable,
|
|
12
13
|
SKIP,
|
|
13
14
|
type StringMap,
|
|
@@ -26,7 +27,7 @@ export interface TransformMapOptions<IN = any, OUT = IN> extends TransformOption
|
|
|
26
27
|
* Defaults to "pass everything" (including null, undefined, etc).
|
|
27
28
|
* Simpler way to exclude certain cases is to return SKIP symbol from the mapper.
|
|
28
29
|
*/
|
|
29
|
-
predicate?: AsyncPredicate<OUT>
|
|
30
|
+
predicate?: Predicate<OUT> | AsyncPredicate<OUT>
|
|
30
31
|
|
|
31
32
|
/**
|
|
32
33
|
* Number of concurrently pending promises returned by `mapper`.
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import { Transform } from 'node:stream'
|
|
2
|
-
import type {
|
|
3
|
-
import type { AsyncIndexedMapper } from '@naturalcycles/js-lib/types'
|
|
2
|
+
import type { AsyncIndexedMapper, IndexedMapper } from '@naturalcycles/js-lib/types'
|
|
4
3
|
import type { TransformOptions, TransformTyped } from '../stream.model.js'
|
|
5
4
|
|
|
6
|
-
export interface TransformTapOptions extends TransformOptions {
|
|
7
|
-
logger?: CommonLogger
|
|
8
|
-
}
|
|
9
|
-
|
|
10
5
|
/**
|
|
11
6
|
* Similar to RxJS `tap` - allows to run a function for each stream item, without affecting the result.
|
|
12
7
|
* Item is passed through to the output.
|
|
@@ -15,17 +10,15 @@ export interface TransformTapOptions extends TransformOptions {
|
|
|
15
10
|
*/
|
|
16
11
|
export function transformTap<IN>(
|
|
17
12
|
fn: AsyncIndexedMapper<IN, any>,
|
|
18
|
-
opt:
|
|
13
|
+
opt: TransformOptions = {},
|
|
19
14
|
): TransformTyped<IN, IN> {
|
|
20
|
-
const { logger = console } = opt
|
|
15
|
+
const { logger = console, highWaterMark = 1 } = opt
|
|
21
16
|
let index = -1
|
|
22
17
|
|
|
23
18
|
return new Transform({
|
|
24
19
|
objectMode: true,
|
|
25
|
-
|
|
20
|
+
highWaterMark,
|
|
26
21
|
async transform(chunk: IN, _, cb) {
|
|
27
|
-
// console.log('tap', chunk)
|
|
28
|
-
|
|
29
22
|
try {
|
|
30
23
|
await fn(chunk, ++index)
|
|
31
24
|
} catch (err) {
|
|
@@ -33,7 +26,33 @@ export function transformTap<IN>(
|
|
|
33
26
|
// suppressed error
|
|
34
27
|
}
|
|
35
28
|
|
|
36
|
-
cb(null, chunk)
|
|
29
|
+
cb(null, chunk)
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Sync version of transformTap
|
|
36
|
+
*/
|
|
37
|
+
export function transformTapSync<IN>(
|
|
38
|
+
fn: IndexedMapper<IN, any>,
|
|
39
|
+
opt: TransformOptions = {},
|
|
40
|
+
): TransformTyped<IN, IN> {
|
|
41
|
+
const { logger = console, highWaterMark = 1 } = opt
|
|
42
|
+
let index = -1
|
|
43
|
+
|
|
44
|
+
return new Transform({
|
|
45
|
+
objectMode: true,
|
|
46
|
+
highWaterMark,
|
|
47
|
+
transform(chunk: IN, _, cb) {
|
|
48
|
+
try {
|
|
49
|
+
fn(chunk, ++index)
|
|
50
|
+
} catch (err) {
|
|
51
|
+
logger.error(err)
|
|
52
|
+
// suppressed error
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
cb(null, chunk)
|
|
37
56
|
},
|
|
38
57
|
})
|
|
39
58
|
}
|