@naturalcycles/nodejs-lib 15.35.0 → 15.36.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
CHANGED
|
@@ -26,5 +26,6 @@ export * from './transform/transformThrottle.js';
|
|
|
26
26
|
export * from './transform/worker/baseWorkerClass.js';
|
|
27
27
|
export * from './transform/worker/transformMultiThreaded.js';
|
|
28
28
|
export * from './transform/worker/transformMultiThreaded.model.js';
|
|
29
|
+
export * from './writable/writableChunk.js';
|
|
29
30
|
export * from './writable/writablePushToArray.js';
|
|
30
31
|
export * from './writable/writableVoid.js';
|
package/dist/stream/index.js
CHANGED
|
@@ -26,5 +26,6 @@ export * from './transform/transformThrottle.js';
|
|
|
26
26
|
export * from './transform/worker/baseWorkerClass.js';
|
|
27
27
|
export * from './transform/worker/transformMultiThreaded.js';
|
|
28
28
|
export * from './transform/worker/transformMultiThreaded.model.js';
|
|
29
|
+
export * from './writable/writableChunk.js';
|
|
29
30
|
export * from './writable/writablePushToArray.js';
|
|
30
31
|
export * from './writable/writableVoid.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Predicate } from '@naturalcycles/js-lib/types';
|
|
2
|
+
import { type TransformOptions, type TransformTyped, type WritableTyped } from '../index.js';
|
|
3
|
+
export interface WritableChunkOptions<T> extends TransformOptions {
|
|
4
|
+
splitPredicate: Predicate<T>;
|
|
5
|
+
transformFactories?: (() => TransformTyped<T, T>)[];
|
|
6
|
+
writableFactory: (splitIndex: number) => WritableTyped<T>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Allows to split the output to multiple files by splitting into chunks
|
|
10
|
+
* based on `shouldSplitFn`.
|
|
11
|
+
* `transformFactories` are used to create a chain of transforms for each chunk.
|
|
12
|
+
* It was meant to be used with createGzip, which needs a proper start and end for each chunk
|
|
13
|
+
* for the output file to be a valid gzip file.
|
|
14
|
+
*/
|
|
15
|
+
export declare function writableChunk<T>(opt: WritableChunkOptions<T>): WritableTyped<T>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Writable } from 'node:stream';
|
|
2
|
+
import { _first, _last } from '@naturalcycles/js-lib/array';
|
|
3
|
+
import { createCommonLoggerAtLevel } from '@naturalcycles/js-lib/log';
|
|
4
|
+
import { _deepCopy } from '@naturalcycles/js-lib/object';
|
|
5
|
+
import { transformNoOp, } from '../index.js';
|
|
6
|
+
/**
|
|
7
|
+
* Allows to split the output to multiple files by splitting into chunks
|
|
8
|
+
* based on `shouldSplitFn`.
|
|
9
|
+
* `transformFactories` are used to create a chain of transforms for each chunk.
|
|
10
|
+
* It was meant to be used with createGzip, which needs a proper start and end for each chunk
|
|
11
|
+
* for the output file to be a valid gzip file.
|
|
12
|
+
*/
|
|
13
|
+
export function writableChunk(opt) {
|
|
14
|
+
const { highWaterMark, splitPredicate, transformFactories = [], writableFactory } = opt;
|
|
15
|
+
const logger = createCommonLoggerAtLevel(opt.logger, opt.logLevel);
|
|
16
|
+
let indexWritten = 0;
|
|
17
|
+
let currentSplitIndex = 0;
|
|
18
|
+
// We don't want to have an empty chain, so we add a no-op transform
|
|
19
|
+
if (transformFactories.length === 0) {
|
|
20
|
+
transformFactories.push((transformNoOp));
|
|
21
|
+
}
|
|
22
|
+
// Create the transforms as well as the Writable, and pipe them together
|
|
23
|
+
let currentWritable = writableFactory(currentSplitIndex);
|
|
24
|
+
let transforms = transformFactories.map(f => f());
|
|
25
|
+
generateTuples(transforms).forEach(([t1, t2]) => t1.pipe(t2));
|
|
26
|
+
_last(transforms).pipe(currentWritable);
|
|
27
|
+
// We keep track of all the pending writables, so we can await them in the final method
|
|
28
|
+
const writablesFinish = [awaitFinish(currentWritable)];
|
|
29
|
+
return new Writable({
|
|
30
|
+
objectMode: true,
|
|
31
|
+
highWaterMark,
|
|
32
|
+
write(chunk, _, cb) {
|
|
33
|
+
// pipe will take care of piping the data through the different streams correctly
|
|
34
|
+
transforms[0].write(chunk, cb);
|
|
35
|
+
if (splitPredicate(chunk, ++indexWritten)) {
|
|
36
|
+
logger.log(`writableChunk: splitting at index ${currentSplitIndex}`);
|
|
37
|
+
currentSplitIndex++;
|
|
38
|
+
transforms[0].end();
|
|
39
|
+
currentWritable = writableFactory(currentSplitIndex);
|
|
40
|
+
transforms = transformFactories.map(f => f());
|
|
41
|
+
generateTuples(transforms).forEach(([t1, t2]) => t1.pipe(t2));
|
|
42
|
+
_last(transforms).pipe(currentWritable);
|
|
43
|
+
writablesFinish.push(awaitFinish(currentWritable));
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
async final(cb) {
|
|
47
|
+
try {
|
|
48
|
+
transforms[0].end();
|
|
49
|
+
await Promise.all(writablesFinish);
|
|
50
|
+
logger.log('writableChunk: all writables are finished');
|
|
51
|
+
cb();
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
cb(err);
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* This is a helper function to create a promise which resolves when the stream emits a 'finish'
|
|
61
|
+
* event.
|
|
62
|
+
* This is used to await all the writables in the final method of the writableChunk
|
|
63
|
+
*/
|
|
64
|
+
async function awaitFinish(stream) {
|
|
65
|
+
return await new Promise(resolve => {
|
|
66
|
+
stream.on('finish', resolve);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Generates an array of [arr[i], arr[i+1]] tuples from the input array.
|
|
71
|
+
* The resulting array will have a length of `arr.length - 1`.
|
|
72
|
+
* ```ts
|
|
73
|
+
* generateTuples([1, 2, 3, 4]) // [[1, 2], [2, 3], [3, 4]]
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
function generateTuples(arr) {
|
|
77
|
+
const tuples = [];
|
|
78
|
+
const arrCopy = _deepCopy(arr);
|
|
79
|
+
for (let i = 1; i < arrCopy.length; i++) {
|
|
80
|
+
tuples.push([arrCopy[i - 1], arrCopy[i]]);
|
|
81
|
+
}
|
|
82
|
+
return tuples;
|
|
83
|
+
}
|
package/package.json
CHANGED
package/src/stream/index.ts
CHANGED
|
@@ -26,5 +26,6 @@ export * from './transform/transformThrottle.js'
|
|
|
26
26
|
export * from './transform/worker/baseWorkerClass.js'
|
|
27
27
|
export * from './transform/worker/transformMultiThreaded.js'
|
|
28
28
|
export * from './transform/worker/transformMultiThreaded.model.js'
|
|
29
|
+
export * from './writable/writableChunk.js'
|
|
29
30
|
export * from './writable/writablePushToArray.js'
|
|
30
31
|
export * from './writable/writableVoid.js'
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Writable } from 'node:stream'
|
|
2
|
+
import { _first, _last } from '@naturalcycles/js-lib/array'
|
|
3
|
+
import { createCommonLoggerAtLevel } from '@naturalcycles/js-lib/log'
|
|
4
|
+
import { _deepCopy } from '@naturalcycles/js-lib/object'
|
|
5
|
+
import type { Predicate } from '@naturalcycles/js-lib/types'
|
|
6
|
+
import {
|
|
7
|
+
transformNoOp,
|
|
8
|
+
type TransformOptions,
|
|
9
|
+
type TransformTyped,
|
|
10
|
+
type WritableTyped,
|
|
11
|
+
} from '../index.js'
|
|
12
|
+
|
|
13
|
+
export interface WritableChunkOptions<T> extends TransformOptions {
|
|
14
|
+
splitPredicate: Predicate<T>
|
|
15
|
+
transformFactories?: (() => TransformTyped<T, T>)[]
|
|
16
|
+
writableFactory: (splitIndex: number) => WritableTyped<T>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Allows to split the output to multiple files by splitting into chunks
|
|
21
|
+
* based on `shouldSplitFn`.
|
|
22
|
+
* `transformFactories` are used to create a chain of transforms for each chunk.
|
|
23
|
+
* It was meant to be used with createGzip, which needs a proper start and end for each chunk
|
|
24
|
+
* for the output file to be a valid gzip file.
|
|
25
|
+
*/
|
|
26
|
+
export function writableChunk<T>(opt: WritableChunkOptions<T>): WritableTyped<T> {
|
|
27
|
+
const { highWaterMark, splitPredicate, transformFactories = [], writableFactory } = opt
|
|
28
|
+
const logger = createCommonLoggerAtLevel(opt.logger, opt.logLevel)
|
|
29
|
+
|
|
30
|
+
let indexWritten = 0
|
|
31
|
+
let currentSplitIndex = 0
|
|
32
|
+
// We don't want to have an empty chain, so we add a no-op transform
|
|
33
|
+
if (transformFactories.length === 0) {
|
|
34
|
+
transformFactories.push(transformNoOp<T>)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Create the transforms as well as the Writable, and pipe them together
|
|
38
|
+
let currentWritable = writableFactory(currentSplitIndex)
|
|
39
|
+
let transforms = transformFactories.map(f => f())
|
|
40
|
+
generateTuples(transforms).forEach(([t1, t2]) => t1.pipe(t2))
|
|
41
|
+
_last(transforms).pipe(currentWritable)
|
|
42
|
+
|
|
43
|
+
// We keep track of all the pending writables, so we can await them in the final method
|
|
44
|
+
const writablesFinish: Promise<void>[] = [awaitFinish(currentWritable)]
|
|
45
|
+
|
|
46
|
+
return new Writable({
|
|
47
|
+
objectMode: true,
|
|
48
|
+
highWaterMark,
|
|
49
|
+
write(chunk: T, _, cb) {
|
|
50
|
+
// pipe will take care of piping the data through the different streams correctly
|
|
51
|
+
transforms[0]!.write(chunk, cb)
|
|
52
|
+
|
|
53
|
+
if (splitPredicate(chunk, ++indexWritten)) {
|
|
54
|
+
logger.log(`writableChunk: splitting at index ${currentSplitIndex}`)
|
|
55
|
+
currentSplitIndex++
|
|
56
|
+
transforms[0]!.end()
|
|
57
|
+
|
|
58
|
+
currentWritable = writableFactory(currentSplitIndex)
|
|
59
|
+
transforms = transformFactories.map(f => f())
|
|
60
|
+
generateTuples(transforms).forEach(([t1, t2]) => t1.pipe(t2))
|
|
61
|
+
_last(transforms).pipe(currentWritable)
|
|
62
|
+
|
|
63
|
+
writablesFinish.push(awaitFinish(currentWritable))
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
async final(cb) {
|
|
67
|
+
try {
|
|
68
|
+
transforms[0]!.end()
|
|
69
|
+
await Promise.all(writablesFinish)
|
|
70
|
+
logger.log('writableChunk: all writables are finished')
|
|
71
|
+
cb()
|
|
72
|
+
} catch (err) {
|
|
73
|
+
cb(err as Error)
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* This is a helper function to create a promise which resolves when the stream emits a 'finish'
|
|
81
|
+
* event.
|
|
82
|
+
* This is used to await all the writables in the final method of the writableChunk
|
|
83
|
+
*/
|
|
84
|
+
async function awaitFinish(stream: Writable): Promise<void> {
|
|
85
|
+
return await new Promise(resolve => {
|
|
86
|
+
stream.on('finish', resolve)
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Generates an array of [arr[i], arr[i+1]] tuples from the input array.
|
|
92
|
+
* The resulting array will have a length of `arr.length - 1`.
|
|
93
|
+
* ```ts
|
|
94
|
+
* generateTuples([1, 2, 3, 4]) // [[1, 2], [2, 3], [3, 4]]
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
function generateTuples<T>(arr: T[]): [T, T][] {
|
|
98
|
+
const tuples: [T, T][] = []
|
|
99
|
+
const arrCopy = _deepCopy(arr)
|
|
100
|
+
for (let i = 1; i < arrCopy.length; i++) {
|
|
101
|
+
tuples.push([arrCopy[i - 1]!, arrCopy[i]!])
|
|
102
|
+
}
|
|
103
|
+
return tuples
|
|
104
|
+
}
|