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.
@@ -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
+ }
@@ -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 {};
@@ -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
+ }