csv-stream-lite 0.1.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/EXAMPLES.md +197 -0
- package/LICENSE +21 -0
- package/README.md +273 -0
- package/dist/byte-buffer.d.ts +112 -0
- package/dist/byte-buffer.js +282 -0
- package/dist/defaults.d.ts +8 -0
- package/dist/defaults.js +8 -0
- package/dist/errors.d.ts +30 -0
- package/dist/errors.js +30 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/parser.d.ts +293 -0
- package/dist/parser.js +596 -0
- package/dist/stringify.d.ts +91 -0
- package/dist/stringify.js +186 -0
- package/dist/types.d.ts +33 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +14 -0
- package/dist/utils.js +20 -0
- package/package.json +69 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
const UTF_8_BOM = '\uFEFF';
|
|
2
|
+
/**
|
|
3
|
+
* CSV stringifier class for converting JavaScript objects into CSV format.
|
|
4
|
+
* Supports both synchronous and asynchronous streaming of CSV output.
|
|
5
|
+
*
|
|
6
|
+
* @typeParam T - The input object type
|
|
7
|
+
* @typeParam O - The output object type after optional transformation (defaults to T)
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const data = [{ name: 'Alice', age: 30 }, { name: 'Bob', age: 25 }]
|
|
12
|
+
* const stringifier = new CsvStringify(data, { headers: ['name', 'age'] })
|
|
13
|
+
* const csvString = stringifier.toString()
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export class CsvStringify {
|
|
17
|
+
values;
|
|
18
|
+
headers;
|
|
19
|
+
delimiter;
|
|
20
|
+
escapeChar;
|
|
21
|
+
quoteChar;
|
|
22
|
+
newline;
|
|
23
|
+
writeBom;
|
|
24
|
+
alwaysWriteHeaders;
|
|
25
|
+
transform;
|
|
26
|
+
/**
|
|
27
|
+
* Creates a new CSV stringifier.
|
|
28
|
+
*
|
|
29
|
+
* @param values - Iterable or async iterable of objects to stringify
|
|
30
|
+
* @param options - Optional configuration for CSV stringification
|
|
31
|
+
*/
|
|
32
|
+
constructor(values, options) {
|
|
33
|
+
this.values = values;
|
|
34
|
+
this.headers = options?.headers
|
|
35
|
+
? Array.isArray(options.headers)
|
|
36
|
+
? options.headers
|
|
37
|
+
: Object.keys(options.headers)
|
|
38
|
+
: undefined;
|
|
39
|
+
this.delimiter = options?.delimiter ?? ',';
|
|
40
|
+
this.escapeChar = options?.escapeChar ?? '"';
|
|
41
|
+
this.quoteChar = options?.quoteChar ?? '"';
|
|
42
|
+
this.newline = options?.newline ?? '\n';
|
|
43
|
+
this.writeBom = options?.writeBom ?? false;
|
|
44
|
+
this.alwaysWriteHeaders = options?.alwaysWriteHeaders ?? false;
|
|
45
|
+
this.transform = options?.transform;
|
|
46
|
+
}
|
|
47
|
+
formatField(field) {
|
|
48
|
+
const needsQuoting = field.includes(this.delimiter) ||
|
|
49
|
+
field.includes('\n') ||
|
|
50
|
+
field.includes('\r') ||
|
|
51
|
+
field.includes(this.quoteChar);
|
|
52
|
+
if (needsQuoting) {
|
|
53
|
+
field = field.replace(new RegExp(this.quoteChar, 'g'), this.escapeChar + this.quoteChar);
|
|
54
|
+
field = this.quoteChar + field + this.quoteChar;
|
|
55
|
+
}
|
|
56
|
+
return field;
|
|
57
|
+
}
|
|
58
|
+
*recordToString(record, firstRecord) {
|
|
59
|
+
let index = 0;
|
|
60
|
+
if (this.transform) {
|
|
61
|
+
record = this.transform(record);
|
|
62
|
+
}
|
|
63
|
+
const recordKeys = Object.keys(record);
|
|
64
|
+
const headers = this.headers ?? recordKeys;
|
|
65
|
+
if (firstRecord) {
|
|
66
|
+
// Output header row
|
|
67
|
+
for (const header of headers) {
|
|
68
|
+
const fieldStr = this.formatField(header);
|
|
69
|
+
if (index > 0) {
|
|
70
|
+
yield this.delimiter;
|
|
71
|
+
}
|
|
72
|
+
yield fieldStr;
|
|
73
|
+
index++;
|
|
74
|
+
}
|
|
75
|
+
yield this.newline;
|
|
76
|
+
index = 0;
|
|
77
|
+
}
|
|
78
|
+
for (let i = 0; i < headers.length; i++) {
|
|
79
|
+
const fieldStr = this.formatField(String(record[recordKeys[i]] ?? ''));
|
|
80
|
+
if (index > 0) {
|
|
81
|
+
yield this.delimiter;
|
|
82
|
+
}
|
|
83
|
+
yield fieldStr;
|
|
84
|
+
index++;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
*forceWriteHeaders() {
|
|
88
|
+
if (this.headers) {
|
|
89
|
+
let index = 0;
|
|
90
|
+
for (const header of this.headers) {
|
|
91
|
+
const fieldStr = this.formatField(header);
|
|
92
|
+
if (index > 0) {
|
|
93
|
+
yield this.delimiter;
|
|
94
|
+
}
|
|
95
|
+
yield fieldStr;
|
|
96
|
+
index++;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Returns a synchronous generator that yields CSV string chunks.
|
|
102
|
+
*
|
|
103
|
+
* @returns A generator that yields CSV strings
|
|
104
|
+
* @throws {Error} If values is not iterable
|
|
105
|
+
*/
|
|
106
|
+
*toStream() {
|
|
107
|
+
if (!(Symbol.iterator in this.values)) {
|
|
108
|
+
throw new Error('Values is not iterable');
|
|
109
|
+
}
|
|
110
|
+
if (this.writeBom) {
|
|
111
|
+
yield UTF_8_BOM;
|
|
112
|
+
}
|
|
113
|
+
let firstRecord = true;
|
|
114
|
+
for (const record of this.values) {
|
|
115
|
+
if (!firstRecord) {
|
|
116
|
+
yield this.newline;
|
|
117
|
+
}
|
|
118
|
+
yield* this.recordToString(record, firstRecord);
|
|
119
|
+
firstRecord = false;
|
|
120
|
+
}
|
|
121
|
+
if (firstRecord && this.alwaysWriteHeaders) {
|
|
122
|
+
yield* this.forceWriteHeaders();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Returns an asynchronous generator that yields CSV string chunks.
|
|
127
|
+
*
|
|
128
|
+
* @returns An async generator that yields CSV strings
|
|
129
|
+
*/
|
|
130
|
+
async *toStreamAsync() {
|
|
131
|
+
if (this.writeBom) {
|
|
132
|
+
yield UTF_8_BOM;
|
|
133
|
+
}
|
|
134
|
+
let firstRecord = true;
|
|
135
|
+
for await (const record of this.values) {
|
|
136
|
+
if (!firstRecord) {
|
|
137
|
+
yield this.newline;
|
|
138
|
+
}
|
|
139
|
+
yield* this.recordToString(record, firstRecord);
|
|
140
|
+
firstRecord = false;
|
|
141
|
+
}
|
|
142
|
+
if (firstRecord && this.alwaysWriteHeaders) {
|
|
143
|
+
yield* this.forceWriteHeaders();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Converts all values to a single CSV string synchronously.
|
|
148
|
+
*
|
|
149
|
+
* @returns The complete CSV string
|
|
150
|
+
*/
|
|
151
|
+
toString() {
|
|
152
|
+
let result = '';
|
|
153
|
+
for (const chunk of this) {
|
|
154
|
+
result += chunk;
|
|
155
|
+
}
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Converts all values to a single CSV string asynchronously.
|
|
160
|
+
*
|
|
161
|
+
* @returns A promise that resolves to the complete CSV string
|
|
162
|
+
*/
|
|
163
|
+
async toStringAsync() {
|
|
164
|
+
let result = '';
|
|
165
|
+
for await (const chunk of this) {
|
|
166
|
+
result += chunk;
|
|
167
|
+
}
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Makes this stringifier iterable using the synchronous stream.
|
|
172
|
+
*
|
|
173
|
+
* @returns A generator that yields CSV strings
|
|
174
|
+
*/
|
|
175
|
+
[Symbol.iterator]() {
|
|
176
|
+
return this.toStream();
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Makes this stringifier async iterable using the asynchronous stream.
|
|
180
|
+
*
|
|
181
|
+
* @returns An async generator that yields CSV strings
|
|
182
|
+
*/
|
|
183
|
+
[Symbol.asyncIterator]() {
|
|
184
|
+
return this.toStreamAsync();
|
|
185
|
+
}
|
|
186
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A utility type to prevent TypeScript from inferring a type parameter.
|
|
3
|
+
*/
|
|
4
|
+
export type CsvString<T = unknown> = string & {
|
|
5
|
+
__csvStringBrand?: T;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Union type representing valid stream input formats.
|
|
9
|
+
*/
|
|
10
|
+
export type StreamInput<T = unknown> = CsvString<T> | number | number[] | Uint8Array;
|
|
11
|
+
/**
|
|
12
|
+
* An async iterable stream of JSON input that can be consumed incrementally.
|
|
13
|
+
* Supports strings, numbers, arrays of numbers, or Uint8Arrays as stream items.
|
|
14
|
+
*/
|
|
15
|
+
export type ByteStream<T = unknown> = AsyncIterable<StreamInput<T>> | Iterable<StreamInput<T>> | ReadableStream<StreamInput<T>>;
|
|
16
|
+
/**
|
|
17
|
+
* Defines the shape of a CSV object by mapping each property key to a transformer function.
|
|
18
|
+
* The transformer function converts a cell string into the appropriate type for that property.
|
|
19
|
+
*
|
|
20
|
+
* @typeParam T - The object type being defined
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* const shape: CsvObjectShape<User> = {
|
|
25
|
+
* name: String,
|
|
26
|
+
* age: Number,
|
|
27
|
+
* active: Boolean
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export type CsvObjectShape<T extends object> = {
|
|
32
|
+
[key in keyof T]: (cell: string) => T[key];
|
|
33
|
+
};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a string to a Uint8Array of UTF-8 encoded bytes.
|
|
3
|
+
*
|
|
4
|
+
* @param str - The string to convert
|
|
5
|
+
* @returns A Uint8Array containing the UTF-8 encoded bytes
|
|
6
|
+
*/
|
|
7
|
+
export declare function stringToBytes(str: string): Uint8Array;
|
|
8
|
+
/**
|
|
9
|
+
* Converts a Uint8Array of UTF-8 encoded bytes to a string.
|
|
10
|
+
*
|
|
11
|
+
* @param bytes - The Uint8Array to decode
|
|
12
|
+
* @returns The decoded string
|
|
13
|
+
*/
|
|
14
|
+
export declare function bytesToString(bytes: Uint8Array): string;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const textEncoder = new TextEncoder();
|
|
2
|
+
const textDecoder = new TextDecoder();
|
|
3
|
+
/**
|
|
4
|
+
* Converts a string to a Uint8Array of UTF-8 encoded bytes.
|
|
5
|
+
*
|
|
6
|
+
* @param str - The string to convert
|
|
7
|
+
* @returns A Uint8Array containing the UTF-8 encoded bytes
|
|
8
|
+
*/
|
|
9
|
+
export function stringToBytes(str) {
|
|
10
|
+
return textEncoder.encode(str);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Converts a Uint8Array of UTF-8 encoded bytes to a string.
|
|
14
|
+
*
|
|
15
|
+
* @param bytes - The Uint8Array to decode
|
|
16
|
+
* @returns The decoded string
|
|
17
|
+
*/
|
|
18
|
+
export function bytesToString(bytes) {
|
|
19
|
+
return textDecoder.decode(bytes);
|
|
20
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "csv-stream-lite",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A lightweight, memory-efficient, zero-dependency streaming JSON parser and stringifier for JavaScript and TypeScript. It works in both Node.js and browser environments.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+ssh://git@github.com/jacobshirley/csv-stream-lite.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://jacobshirley.github.io/csv-stream-lite/v1",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/jacobshirley/csv-stream-lite/issues"
|
|
14
|
+
},
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"default": "./dist/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./*": {
|
|
21
|
+
"types": "./dist/*.d.ts",
|
|
22
|
+
"default": "./dist/*"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"json",
|
|
27
|
+
"stream",
|
|
28
|
+
"streaming",
|
|
29
|
+
"parser",
|
|
30
|
+
"stringify",
|
|
31
|
+
"parse",
|
|
32
|
+
"incremental",
|
|
33
|
+
"async",
|
|
34
|
+
"memory-efficient",
|
|
35
|
+
"low-memory",
|
|
36
|
+
"large-files",
|
|
37
|
+
"json-parser",
|
|
38
|
+
"csv-stream",
|
|
39
|
+
"streaming-json",
|
|
40
|
+
"lightweight",
|
|
41
|
+
"zero-dependencies",
|
|
42
|
+
"typescript",
|
|
43
|
+
"browser",
|
|
44
|
+
"nodejs"
|
|
45
|
+
],
|
|
46
|
+
"author": "Jacob Shirley",
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@vitest/browser": "^4.0.16",
|
|
50
|
+
"@vitest/browser-playwright": "^4.0.16",
|
|
51
|
+
"@vitest/coverage-v8": "^4.0.16",
|
|
52
|
+
"playwright": "^1.57.0",
|
|
53
|
+
"typescript": "^5.9.3",
|
|
54
|
+
"vitest": "^4.0.16"
|
|
55
|
+
},
|
|
56
|
+
"files": [
|
|
57
|
+
"dist",
|
|
58
|
+
"!**/*.tsbuildinfo",
|
|
59
|
+
"EXAMPLES.md"
|
|
60
|
+
],
|
|
61
|
+
"scripts": {
|
|
62
|
+
"test:unit": "vitest run test/unit",
|
|
63
|
+
"test": "vitest run",
|
|
64
|
+
"compile": "pnpm compile:validate && tsc -p tsconfig.prod.json",
|
|
65
|
+
"compile:validate": "tsc -p tsconfig.json --noEmit",
|
|
66
|
+
"watch": "tsc -watch -p tsconfig.prod.json",
|
|
67
|
+
"watch:all": "tsc -watch -p tsconfig.json"
|
|
68
|
+
}
|
|
69
|
+
}
|