forked from Commitlabs-Org/Commitlabs-Frontend
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcsv.ts
More file actions
70 lines (59 loc) · 2.08 KB
/
Copy pathcsv.ts
File metadata and controls
70 lines (59 loc) · 2.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
const FORMULA_PREFIX_PATTERN = /^[=+\-@]/;
export type CsvRow = Array<string | null | undefined>;
export function escapeCsvField(value: string | null | undefined): string {
const normalizedValue = value == null ? '' : String(value);
const safeValue = FORMULA_PREFIX_PATTERN.test(normalizedValue)
? `'${normalizedValue}`
: normalizedValue;
const escapedValue = safeValue.replace(/"/g, '""');
const shouldWrap =
escapedValue.includes(',') ||
escapedValue.includes('"') ||
escapedValue.includes('\n') ||
/^[\s]|[\s]$/.test(escapedValue);
return shouldWrap ? `"${escapedValue}"` : escapedValue;
}
/**
* Formats a single CSV row and terminates it with CRLF.
*/
export function formatCsvRow(row: CsvRow): string {
return `${row.map(escapeCsvField).join(',')}\r\n`;
}
export function buildCsv(headers: string[], rows: CsvRow[]): string {
return [headers, ...rows].map(formatCsvRow).join('');
}
function isAsyncIterable<T>(value: unknown): value is AsyncIterable<T> {
return typeof value === 'object' && value !== null && Symbol.asyncIterator in value;
}
/**
* Streams a CSV with a deterministic header first, then row-by-row chunks.
* The caller should bound the number of rows before calling this helper so the
* response cannot grow without a defined upper bound.
*/
export function createCsvStream(
headers: string[],
rows: Iterable<CsvRow> | AsyncIterable<CsvRow>,
): ReadableStream<Uint8Array> {
const encoder = new TextEncoder();
return new ReadableStream<Uint8Array>({
async start(controller) {
try {
controller.enqueue(encoder.encode(formatCsvRow(headers)));
const iterator = isAsyncIterable<CsvRow>(rows)
? rows[Symbol.asyncIterator]()
: rows[Symbol.iterator]();
for await (const row of {
next: iterator.next.bind(iterator),
[Symbol.asyncIterator]() {
return this;
},
}) {
controller.enqueue(encoder.encode(formatCsvRow(row)));
}
controller.close();
} catch (error) {
controller.error(error);
}
},
});
}