@naturalcycles/nodejs-lib 12.88.0 → 12.90.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/csv/csvReader.d.ts +16 -0
- package/dist/csv/csvReader.js +49 -0
- package/dist/csv/csvWriter.d.ts +32 -0
- package/dist/csv/csvWriter.js +57 -0
- package/dist/csv/transformToCSV.d.ts +20 -0
- package/dist/csv/transformToCSV.js +37 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/stream/ndjson/transformToNDJson.d.ts +0 -4
- package/dist/stream/ndjson/transformToNDJson.js +2 -15
- package/package.json +1 -1
- package/src/csv/csvReader.ts +72 -0
- package/src/csv/csvWriter.ts +81 -0
- package/src/csv/transformToCSV.ts +54 -0
- package/src/index.ts +3 -0
- package/src/stream/ndjson/transformToNDJson.ts +2 -20
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AnyObject } from '@naturalcycles/js-lib';
|
|
2
|
+
export interface CSVReaderConfig {
|
|
3
|
+
/**
|
|
4
|
+
* Default: comma
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Default: true
|
|
8
|
+
*/
|
|
9
|
+
firstRowIsHeader?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Array of columns, to be used if there is no header row.
|
|
12
|
+
*/
|
|
13
|
+
columns?: string[];
|
|
14
|
+
}
|
|
15
|
+
export declare function csvStringParse<T extends AnyObject = any>(str: string, cfg?: CSVReaderConfig): T[];
|
|
16
|
+
export declare function csvStringToArray(str: string): string[][];
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Inspired by: https://gist.github.com/Jezternz/c8e9fafc2c114e079829974e3764db75
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.csvStringToArray = exports.csvStringParse = void 0;
|
|
5
|
+
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
6
|
+
// export class CSVReader {
|
|
7
|
+
// constructor (cfg: CSVReaderConfig) {
|
|
8
|
+
// this.cfg = {
|
|
9
|
+
// delimiter: ',',
|
|
10
|
+
// includeHeader: true,
|
|
11
|
+
// ...cfg,
|
|
12
|
+
// }
|
|
13
|
+
// }
|
|
14
|
+
//
|
|
15
|
+
// public cfg: Required<CSVReaderConfig>
|
|
16
|
+
// }
|
|
17
|
+
function csvStringParse(str, cfg = {}) {
|
|
18
|
+
const { firstRowIsHeader = true, columns } = cfg;
|
|
19
|
+
const arr = csvStringToArray(str);
|
|
20
|
+
let header = columns;
|
|
21
|
+
if (firstRowIsHeader) {
|
|
22
|
+
const firstRow = arr.shift();
|
|
23
|
+
header ||= firstRow;
|
|
24
|
+
}
|
|
25
|
+
(0, js_lib_1._assert)(header, `firstRowIsHeader or columns is required`);
|
|
26
|
+
return arr.map(row => {
|
|
27
|
+
// eslint-disable-next-line unicorn/no-array-reduce
|
|
28
|
+
return header.reduce((obj, col, i) => {
|
|
29
|
+
;
|
|
30
|
+
obj[col] = row[i];
|
|
31
|
+
return obj;
|
|
32
|
+
}, {});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
exports.csvStringParse = csvStringParse;
|
|
36
|
+
function csvStringToArray(str) {
|
|
37
|
+
const objPattern = new RegExp('(,|\\r?\\n|\\r|^)(?:"([^"]*(?:""[^"]*)*)"|([^,\\r\\n]*))', 'gi');
|
|
38
|
+
let matches = null;
|
|
39
|
+
const arr = [[]];
|
|
40
|
+
// eslint-disable-next-line no-cond-assign
|
|
41
|
+
while ((matches = objPattern.exec(str))) {
|
|
42
|
+
if (matches[1].length && matches[1] !== ',') {
|
|
43
|
+
arr.push([]);
|
|
44
|
+
}
|
|
45
|
+
arr[arr.length - 1].push(matches[2] ? matches[2].replace(new RegExp('""', 'g'), '"') : matches[3]);
|
|
46
|
+
}
|
|
47
|
+
return arr;
|
|
48
|
+
}
|
|
49
|
+
exports.csvStringToArray = csvStringToArray;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { AnyObject } from '@naturalcycles/js-lib';
|
|
2
|
+
export interface CSVWriterConfig {
|
|
3
|
+
/**
|
|
4
|
+
* Default: comma
|
|
5
|
+
*/
|
|
6
|
+
delimiter?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Array of columns
|
|
9
|
+
*/
|
|
10
|
+
columns?: string[];
|
|
11
|
+
/**
|
|
12
|
+
* Default: true
|
|
13
|
+
*/
|
|
14
|
+
includeHeader?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare class CSVWriter {
|
|
17
|
+
constructor(cfg: CSVWriterConfig);
|
|
18
|
+
cfg: CSVWriterConfig & {
|
|
19
|
+
delimiter: string;
|
|
20
|
+
};
|
|
21
|
+
writeRows(rows: AnyObject[]): string;
|
|
22
|
+
writeHeader(): string;
|
|
23
|
+
writeRow(row: AnyObject): string;
|
|
24
|
+
private quoteIfNeeded;
|
|
25
|
+
private quote;
|
|
26
|
+
private shouldQuote;
|
|
27
|
+
}
|
|
28
|
+
export declare function arrayToCSVString(arr: AnyObject[], cfg?: CSVWriterConfig): string;
|
|
29
|
+
/**
|
|
30
|
+
* Iterates over the whole array and notes all possible columns.
|
|
31
|
+
*/
|
|
32
|
+
export declare function arrayToCSVColumns(arr: AnyObject[]): string[];
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Inspired by: https://github.com/ryu1kn/csv-writer/
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.arrayToCSVColumns = exports.arrayToCSVString = exports.CSVWriter = void 0;
|
|
5
|
+
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
6
|
+
class CSVWriter {
|
|
7
|
+
constructor(cfg) {
|
|
8
|
+
this.cfg = {
|
|
9
|
+
delimiter: ',',
|
|
10
|
+
includeHeader: true,
|
|
11
|
+
...cfg,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
writeRows(rows) {
|
|
15
|
+
let s = '';
|
|
16
|
+
if (this.cfg.includeHeader) {
|
|
17
|
+
// Detect columns based on content, if not defined upfront
|
|
18
|
+
this.cfg.columns ||= arrayToCSVColumns(rows);
|
|
19
|
+
s += this.writeHeader() + '\n';
|
|
20
|
+
}
|
|
21
|
+
return s + rows.map(row => this.writeRow(row)).join('\n');
|
|
22
|
+
}
|
|
23
|
+
writeHeader() {
|
|
24
|
+
(0, js_lib_1._assert)(this.cfg.columns, 'CSVWriter cannot writeHeader, because columns were not provided');
|
|
25
|
+
return this.cfg.columns.map(col => this.quoteIfNeeded(col)).join(this.cfg.delimiter);
|
|
26
|
+
}
|
|
27
|
+
writeRow(row) {
|
|
28
|
+
(0, js_lib_1._assert)(this.cfg.columns, 'CSVWriter cannot writeRow, because columns were not provided');
|
|
29
|
+
return this.cfg.columns
|
|
30
|
+
.map(col => this.quoteIfNeeded(String(row[col] ?? '')))
|
|
31
|
+
.join(this.cfg.delimiter);
|
|
32
|
+
}
|
|
33
|
+
quoteIfNeeded(s) {
|
|
34
|
+
return this.shouldQuote(s) ? this.quote(s) : s;
|
|
35
|
+
}
|
|
36
|
+
quote(s) {
|
|
37
|
+
return `"${s.replace(/"/g, '""')}"`;
|
|
38
|
+
}
|
|
39
|
+
shouldQuote(s) {
|
|
40
|
+
return s.includes(this.cfg.delimiter) || s.includes('"') || s.includes('\n') || s.includes('\r');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.CSVWriter = CSVWriter;
|
|
44
|
+
function arrayToCSVString(arr, cfg = {}) {
|
|
45
|
+
const writer = new CSVWriter(cfg);
|
|
46
|
+
return writer.writeRows(arr);
|
|
47
|
+
}
|
|
48
|
+
exports.arrayToCSVString = arrayToCSVString;
|
|
49
|
+
/**
|
|
50
|
+
* Iterates over the whole array and notes all possible columns.
|
|
51
|
+
*/
|
|
52
|
+
function arrayToCSVColumns(arr) {
|
|
53
|
+
const cols = new Set();
|
|
54
|
+
arr.forEach(row => Object.keys(row).forEach(col => cols.add(col)));
|
|
55
|
+
return [...cols];
|
|
56
|
+
}
|
|
57
|
+
exports.arrayToCSVColumns = arrayToCSVColumns;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { AnyObject } from '@naturalcycles/js-lib';
|
|
2
|
+
import { TransformTyped } from '../stream/stream.model';
|
|
3
|
+
import { CSVWriterConfig } from './csvWriter';
|
|
4
|
+
export interface TransformToCSVOptions extends CSVWriterConfig {
|
|
5
|
+
/**
|
|
6
|
+
* If true - will throw an error on stringify error
|
|
7
|
+
*
|
|
8
|
+
* @default true
|
|
9
|
+
*/
|
|
10
|
+
strict?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Transforms objects (objectMode=true) into chunks \n-terminated CSV strings (readableObjectMode=false).
|
|
14
|
+
*/
|
|
15
|
+
export declare function transformToCSV<IN extends AnyObject = any>(opt: TransformToCSVOptions & {
|
|
16
|
+
/**
|
|
17
|
+
* Columns are required, as they cannot be detected on the fly.
|
|
18
|
+
*/
|
|
19
|
+
columns: string[];
|
|
20
|
+
}): TransformTyped<IN, string>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.transformToCSV = void 0;
|
|
4
|
+
const node_stream_1 = require("node:stream");
|
|
5
|
+
const csvWriter_1 = require("./csvWriter");
|
|
6
|
+
/**
|
|
7
|
+
* Transforms objects (objectMode=true) into chunks \n-terminated CSV strings (readableObjectMode=false).
|
|
8
|
+
*/
|
|
9
|
+
function transformToCSV(opt) {
|
|
10
|
+
const { strict = true } = opt;
|
|
11
|
+
const writer = new csvWriter_1.CSVWriter(opt);
|
|
12
|
+
let firstRow = true;
|
|
13
|
+
return new node_stream_1.Transform({
|
|
14
|
+
writableObjectMode: true,
|
|
15
|
+
readableObjectMode: false,
|
|
16
|
+
transform(chunk, _, cb) {
|
|
17
|
+
try {
|
|
18
|
+
let s = '';
|
|
19
|
+
if (firstRow) {
|
|
20
|
+
s = writer.writeHeader() + '\n';
|
|
21
|
+
firstRow = false;
|
|
22
|
+
}
|
|
23
|
+
cb(null, s + writer.writeRow(chunk) + '\n');
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
console.error(err);
|
|
27
|
+
if (strict) {
|
|
28
|
+
cb(err); // emit error
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
cb(); // emit no error, but no result neither
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
exports.transformToCSV = transformToCSV;
|
package/dist/index.d.ts
CHANGED
|
@@ -58,6 +58,9 @@ export * from './stream/writable/writableForEach';
|
|
|
58
58
|
export * from './stream/writable/writableFork';
|
|
59
59
|
export * from './stream/writable/writablePushToArray';
|
|
60
60
|
export * from './stream/writable/writableVoid';
|
|
61
|
+
export * from './csv/csvWriter';
|
|
62
|
+
export * from './csv/csvReader';
|
|
63
|
+
export * from './csv/transformToCSV';
|
|
61
64
|
export * from './string/inspectAny';
|
|
62
65
|
export * from './util/env.util';
|
|
63
66
|
export * from './util/lruMemoCache';
|
package/dist/index.js
CHANGED
|
@@ -63,6 +63,9 @@ tslib_1.__exportStar(require("./stream/writable/writableForEach"), exports);
|
|
|
63
63
|
tslib_1.__exportStar(require("./stream/writable/writableFork"), exports);
|
|
64
64
|
tslib_1.__exportStar(require("./stream/writable/writablePushToArray"), exports);
|
|
65
65
|
tslib_1.__exportStar(require("./stream/writable/writableVoid"), exports);
|
|
66
|
+
tslib_1.__exportStar(require("./csv/csvWriter"), exports);
|
|
67
|
+
tslib_1.__exportStar(require("./csv/csvReader"), exports);
|
|
68
|
+
tslib_1.__exportStar(require("./csv/transformToCSV"), exports);
|
|
66
69
|
tslib_1.__exportStar(require("./string/inspectAny"), exports);
|
|
67
70
|
tslib_1.__exportStar(require("./util/env.util"), exports);
|
|
68
71
|
tslib_1.__exportStar(require("./util/lruMemoCache"), exports);
|
|
@@ -16,10 +16,6 @@ export interface TransformToNDJsonOptions {
|
|
|
16
16
|
* @default `\n`
|
|
17
17
|
*/
|
|
18
18
|
separator?: string;
|
|
19
|
-
/**
|
|
20
|
-
* @experimental
|
|
21
|
-
*/
|
|
22
|
-
useFlatstr?: boolean;
|
|
23
19
|
}
|
|
24
20
|
/**
|
|
25
21
|
* Transforms objects (objectMode=true) into chunks \n-terminated JSON strings (readableObjectMode=false).
|
|
@@ -7,7 +7,7 @@ const js_lib_1 = require("@naturalcycles/js-lib");
|
|
|
7
7
|
* Transforms objects (objectMode=true) into chunks \n-terminated JSON strings (readableObjectMode=false).
|
|
8
8
|
*/
|
|
9
9
|
function transformToNDJson(opt = {}) {
|
|
10
|
-
const { strict = true, separator = '\n', sortObjects = false
|
|
10
|
+
const { strict = true, separator = '\n', sortObjects = false } = opt;
|
|
11
11
|
return new node_stream_1.Transform({
|
|
12
12
|
writableObjectMode: true,
|
|
13
13
|
readableObjectMode: false,
|
|
@@ -16,12 +16,7 @@ function transformToNDJson(opt = {}) {
|
|
|
16
16
|
if (sortObjects) {
|
|
17
17
|
chunk = (0, js_lib_1._sortObjectDeep)(chunk);
|
|
18
18
|
}
|
|
19
|
-
|
|
20
|
-
cb(null, flatstr(JSON.stringify(chunk) + separator));
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
23
|
-
cb(null, JSON.stringify(chunk) + separator);
|
|
24
|
-
}
|
|
19
|
+
cb(null, JSON.stringify(chunk) + separator);
|
|
25
20
|
}
|
|
26
21
|
catch (err) {
|
|
27
22
|
console.error(err);
|
|
@@ -36,11 +31,3 @@ function transformToNDJson(opt = {}) {
|
|
|
36
31
|
});
|
|
37
32
|
}
|
|
38
33
|
exports.transformToNDJson = transformToNDJson;
|
|
39
|
-
/**
|
|
40
|
-
* Based on: https://github.com/davidmarkclements/flatstr/blob/master/index.js
|
|
41
|
-
*/
|
|
42
|
-
function flatstr(s) {
|
|
43
|
-
// eslint-disable-next-line
|
|
44
|
-
s | 0;
|
|
45
|
-
return s;
|
|
46
|
-
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Inspired by: https://gist.github.com/Jezternz/c8e9fafc2c114e079829974e3764db75
|
|
2
|
+
|
|
3
|
+
import { _assert, AnyObject } from '@naturalcycles/js-lib'
|
|
4
|
+
|
|
5
|
+
export interface CSVReaderConfig {
|
|
6
|
+
/**
|
|
7
|
+
* Default: comma
|
|
8
|
+
*/
|
|
9
|
+
// delimiter?: string
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Default: true
|
|
13
|
+
*/
|
|
14
|
+
firstRowIsHeader?: boolean
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Array of columns, to be used if there is no header row.
|
|
18
|
+
*/
|
|
19
|
+
columns?: string[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// export class CSVReader {
|
|
23
|
+
// constructor (cfg: CSVReaderConfig) {
|
|
24
|
+
// this.cfg = {
|
|
25
|
+
// delimiter: ',',
|
|
26
|
+
// includeHeader: true,
|
|
27
|
+
// ...cfg,
|
|
28
|
+
// }
|
|
29
|
+
// }
|
|
30
|
+
//
|
|
31
|
+
// public cfg: Required<CSVReaderConfig>
|
|
32
|
+
// }
|
|
33
|
+
|
|
34
|
+
export function csvStringParse<T extends AnyObject = any>(
|
|
35
|
+
str: string,
|
|
36
|
+
cfg: CSVReaderConfig = {},
|
|
37
|
+
): T[] {
|
|
38
|
+
const { firstRowIsHeader = true, columns } = cfg
|
|
39
|
+
const arr = csvStringToArray(str)
|
|
40
|
+
|
|
41
|
+
let header = columns
|
|
42
|
+
if (firstRowIsHeader) {
|
|
43
|
+
const firstRow = arr.shift()
|
|
44
|
+
header ||= firstRow
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
_assert(header, `firstRowIsHeader or columns is required`)
|
|
48
|
+
|
|
49
|
+
return arr.map(row => {
|
|
50
|
+
// eslint-disable-next-line unicorn/no-array-reduce
|
|
51
|
+
return header!.reduce((obj, col, i) => {
|
|
52
|
+
;(obj as any)[col] = row[i]
|
|
53
|
+
return obj
|
|
54
|
+
}, {} as T)
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function csvStringToArray(str: string): string[][] {
|
|
59
|
+
const objPattern = new RegExp('(,|\\r?\\n|\\r|^)(?:"([^"]*(?:""[^"]*)*)"|([^,\\r\\n]*))', 'gi')
|
|
60
|
+
let matches = null
|
|
61
|
+
const arr: any[][] = [[]]
|
|
62
|
+
// eslint-disable-next-line no-cond-assign
|
|
63
|
+
while ((matches = objPattern.exec(str))) {
|
|
64
|
+
if (matches[1]!.length && matches[1] !== ',') {
|
|
65
|
+
arr.push([])
|
|
66
|
+
}
|
|
67
|
+
arr[arr.length - 1]!.push(
|
|
68
|
+
matches[2] ? matches[2].replace(new RegExp('""', 'g'), '"') : matches[3],
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
return arr
|
|
72
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Inspired by: https://github.com/ryu1kn/csv-writer/
|
|
2
|
+
|
|
3
|
+
import { _assert, AnyObject } from '@naturalcycles/js-lib'
|
|
4
|
+
|
|
5
|
+
export interface CSVWriterConfig {
|
|
6
|
+
/**
|
|
7
|
+
* Default: comma
|
|
8
|
+
*/
|
|
9
|
+
delimiter?: string
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Array of columns
|
|
13
|
+
*/
|
|
14
|
+
columns?: string[]
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Default: true
|
|
18
|
+
*/
|
|
19
|
+
includeHeader?: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class CSVWriter {
|
|
23
|
+
constructor(cfg: CSVWriterConfig) {
|
|
24
|
+
this.cfg = {
|
|
25
|
+
delimiter: ',',
|
|
26
|
+
includeHeader: true,
|
|
27
|
+
...cfg,
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public cfg: CSVWriterConfig & { delimiter: string }
|
|
32
|
+
|
|
33
|
+
writeRows(rows: AnyObject[]): string {
|
|
34
|
+
let s = ''
|
|
35
|
+
if (this.cfg.includeHeader) {
|
|
36
|
+
// Detect columns based on content, if not defined upfront
|
|
37
|
+
this.cfg.columns ||= arrayToCSVColumns(rows)
|
|
38
|
+
|
|
39
|
+
s += this.writeHeader() + '\n'
|
|
40
|
+
}
|
|
41
|
+
return s + rows.map(row => this.writeRow(row)).join('\n')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
writeHeader(): string {
|
|
45
|
+
_assert(this.cfg.columns, 'CSVWriter cannot writeHeader, because columns were not provided')
|
|
46
|
+
return this.cfg.columns.map(col => this.quoteIfNeeded(col)).join(this.cfg.delimiter)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
writeRow(row: AnyObject): string {
|
|
50
|
+
_assert(this.cfg.columns, 'CSVWriter cannot writeRow, because columns were not provided')
|
|
51
|
+
return this.cfg.columns
|
|
52
|
+
.map(col => this.quoteIfNeeded(String(row[col] ?? '')))
|
|
53
|
+
.join(this.cfg.delimiter)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private quoteIfNeeded(s: string): string {
|
|
57
|
+
return this.shouldQuote(s) ? this.quote(s) : s
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private quote(s: string): string {
|
|
61
|
+
return `"${s.replace(/"/g, '""')}"`
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private shouldQuote(s: string): boolean {
|
|
65
|
+
return s.includes(this.cfg.delimiter) || s.includes('"') || s.includes('\n') || s.includes('\r')
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function arrayToCSVString(arr: AnyObject[], cfg: CSVWriterConfig = {}): string {
|
|
70
|
+
const writer = new CSVWriter(cfg)
|
|
71
|
+
return writer.writeRows(arr)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Iterates over the whole array and notes all possible columns.
|
|
76
|
+
*/
|
|
77
|
+
export function arrayToCSVColumns(arr: AnyObject[]): string[] {
|
|
78
|
+
const cols = new Set<string>()
|
|
79
|
+
arr.forEach(row => Object.keys(row).forEach(col => cols.add(col)))
|
|
80
|
+
return [...cols]
|
|
81
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Transform } from 'node:stream'
|
|
2
|
+
import { AnyObject } from '@naturalcycles/js-lib'
|
|
3
|
+
import { TransformTyped } from '../stream/stream.model'
|
|
4
|
+
import { CSVWriter, CSVWriterConfig } from './csvWriter'
|
|
5
|
+
|
|
6
|
+
export interface TransformToCSVOptions extends CSVWriterConfig {
|
|
7
|
+
/**
|
|
8
|
+
* If true - will throw an error on stringify error
|
|
9
|
+
*
|
|
10
|
+
* @default true
|
|
11
|
+
*/
|
|
12
|
+
strict?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Transforms objects (objectMode=true) into chunks \n-terminated CSV strings (readableObjectMode=false).
|
|
17
|
+
*/
|
|
18
|
+
export function transformToCSV<IN extends AnyObject = any>(
|
|
19
|
+
opt: TransformToCSVOptions & {
|
|
20
|
+
/**
|
|
21
|
+
* Columns are required, as they cannot be detected on the fly.
|
|
22
|
+
*/
|
|
23
|
+
columns: string[]
|
|
24
|
+
},
|
|
25
|
+
): TransformTyped<IN, string> {
|
|
26
|
+
const { strict = true } = opt
|
|
27
|
+
const writer = new CSVWriter(opt)
|
|
28
|
+
let firstRow = true
|
|
29
|
+
|
|
30
|
+
return new Transform({
|
|
31
|
+
writableObjectMode: true,
|
|
32
|
+
readableObjectMode: false,
|
|
33
|
+
transform(chunk: IN, _, cb) {
|
|
34
|
+
try {
|
|
35
|
+
let s = ''
|
|
36
|
+
|
|
37
|
+
if (firstRow) {
|
|
38
|
+
s = writer.writeHeader() + '\n'
|
|
39
|
+
firstRow = false
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
cb(null, s + writer.writeRow(chunk) + '\n')
|
|
43
|
+
} catch (err) {
|
|
44
|
+
console.error(err)
|
|
45
|
+
|
|
46
|
+
if (strict) {
|
|
47
|
+
cb(err as Error) // emit error
|
|
48
|
+
} else {
|
|
49
|
+
cb() // emit no error, but no result neither
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
})
|
|
54
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -58,6 +58,9 @@ export * from './stream/writable/writableForEach'
|
|
|
58
58
|
export * from './stream/writable/writableFork'
|
|
59
59
|
export * from './stream/writable/writablePushToArray'
|
|
60
60
|
export * from './stream/writable/writableVoid'
|
|
61
|
+
export * from './csv/csvWriter'
|
|
62
|
+
export * from './csv/csvReader'
|
|
63
|
+
export * from './csv/transformToCSV'
|
|
61
64
|
export * from './string/inspectAny'
|
|
62
65
|
export * from './util/env.util'
|
|
63
66
|
export * from './util/lruMemoCache'
|
|
@@ -21,11 +21,6 @@ export interface TransformToNDJsonOptions {
|
|
|
21
21
|
* @default `\n`
|
|
22
22
|
*/
|
|
23
23
|
separator?: string
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* @experimental
|
|
27
|
-
*/
|
|
28
|
-
useFlatstr?: boolean
|
|
29
24
|
}
|
|
30
25
|
|
|
31
26
|
/**
|
|
@@ -34,7 +29,7 @@ export interface TransformToNDJsonOptions {
|
|
|
34
29
|
export function transformToNDJson<IN = any>(
|
|
35
30
|
opt: TransformToNDJsonOptions = {},
|
|
36
31
|
): TransformTyped<IN, string> {
|
|
37
|
-
const { strict = true, separator = '\n', sortObjects = false
|
|
32
|
+
const { strict = true, separator = '\n', sortObjects = false } = opt
|
|
38
33
|
|
|
39
34
|
return new Transform({
|
|
40
35
|
writableObjectMode: true,
|
|
@@ -45,11 +40,7 @@ export function transformToNDJson<IN = any>(
|
|
|
45
40
|
chunk = _sortObjectDeep(chunk as any)
|
|
46
41
|
}
|
|
47
42
|
|
|
48
|
-
|
|
49
|
-
cb(null, flatstr(JSON.stringify(chunk) + separator))
|
|
50
|
-
} else {
|
|
51
|
-
cb(null, JSON.stringify(chunk) + separator)
|
|
52
|
-
}
|
|
43
|
+
cb(null, JSON.stringify(chunk) + separator)
|
|
53
44
|
} catch (err) {
|
|
54
45
|
console.error(err)
|
|
55
46
|
|
|
@@ -62,12 +53,3 @@ export function transformToNDJson<IN = any>(
|
|
|
62
53
|
},
|
|
63
54
|
})
|
|
64
55
|
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Based on: https://github.com/davidmarkclements/flatstr/blob/master/index.js
|
|
68
|
-
*/
|
|
69
|
-
function flatstr(s: any): string {
|
|
70
|
-
// eslint-disable-next-line
|
|
71
|
-
s | 0
|
|
72
|
-
return s
|
|
73
|
-
}
|