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 ADDED
@@ -0,0 +1,197 @@
1
+ # csv-stream-Lite Examples
2
+
3
+ This directory contains example scripts demonstrating how to use the csv-stream-Lite library.
4
+
5
+ ## Basic CSV streaming example
6
+
7
+ ```typescript
8
+ import { Csv } from 'csv-stream-lite'
9
+
10
+ const csvData = `name,age,city
11
+ Alice,30,New York
12
+ Bob,25,Los Angeles
13
+ Charlie,35,Chicago`
14
+
15
+ for await (const row of new Csv(csvData).streamObjectsAsync()) {
16
+ console.log(row)
17
+ }
18
+
19
+ // Output:
20
+ // { name: 'Alice', age: '30', city: 'New York' }
21
+ // { name: 'Bob', age: '25', city: 'Los Angeles' }
22
+ // { name: 'Charlie', age: '35', city: 'Chicago' }
23
+ ```
24
+
25
+ ## Low-level CSV streaming example
26
+
27
+ ```typescript
28
+ import { Csv } from 'csv-stream-lite'
29
+
30
+ const csvData = `name,age,city
31
+ Alice,30,New York
32
+ Bob,25,Los Angeles
33
+ Charlie,35,Chicago`
34
+
35
+ let i = 0
36
+
37
+ const csvStream = new Csv(csvData)
38
+ for await (const row of csvStream) {
39
+ for await (const cell of row) {
40
+ if (i++ % 2 === 0) {
41
+ continue
42
+ }
43
+ for await (const char of cell) {
44
+ // Write each character to stdout as we read it
45
+ process.stdout.write(char)
46
+ }
47
+ process.stdout.write('\n')
48
+ }
49
+ }
50
+
51
+ // Output:
52
+ // age
53
+ // Alice
54
+ // New York
55
+ // 25
56
+ // Charlie
57
+ // Chicago
58
+
59
+ // Note: There are no delimiters or newlines in the output because we are
60
+ // writing each cell directly as we read it.
61
+ ```
62
+
63
+ ## Type Coercion Example
64
+
65
+ ```typescript
66
+ import { Csv } from 'csv-stream-lite'
67
+
68
+ const csvData = `name,age,city
69
+ Alice,30,New York
70
+ Bob,25,Los Angeles
71
+ Charlie,35,Chicago`
72
+
73
+ // Define a schema for type coercion
74
+ const schema = {
75
+ name: String,
76
+ age: Number,
77
+ city: String,
78
+ }
79
+
80
+ for await (const row of new Csv(csvData, {
81
+ shape: schema,
82
+ }).streamObjectsAsync()) {
83
+ console.log(row)
84
+ // TypeScript infers the type of `row` as:
85
+ // {
86
+ // name: string;
87
+ // age: number;
88
+ // city: string;
89
+ // }
90
+ }
91
+
92
+ // Output:
93
+ // { name: 'Alice', age: 30, city: 'New York' }
94
+ // { name: 'Bob', age: 25, city: 'Los Angeles' }
95
+ // { name: 'Charlie', age: 35, city: 'Chicago' }
96
+ ```
97
+
98
+ ## Object to CSV stringification example
99
+
100
+ ```typescript
101
+ import { Csv } from 'csv-stream-lite'
102
+
103
+ const data = [
104
+ { name: 'Alice', age: 30, city: 'New York' },
105
+ { name: 'Bob', age: 25, city: 'Los Angeles' },
106
+ { name: 'Charlie', age: 35, city: 'Chicago' },
107
+ ]
108
+
109
+ const csvString = Csv.stringifier(data).toString()
110
+ console.log(csvString)
111
+
112
+ // Output:
113
+ // name,age,city
114
+ // Alice,30,New York
115
+ // Bob,25,Los Angeles
116
+ // Charlie,35,Chicago
117
+ ```
118
+
119
+ ## Object to CSV stringification streaming example
120
+
121
+ ```typescript
122
+ import { Csv } from 'csv-stream-lite'
123
+
124
+ const data = [
125
+ { name: 'Alice', age: 30, city: 'New York' },
126
+ { name: 'Bob', age: 25, city: 'Los Angeles' },
127
+ { name: 'Charlie', age: 35, city: 'Chicago' },
128
+ ]
129
+
130
+ const csvStream = Csv.stringify(data)
131
+ for (const chunk of csvStream) {
132
+ process.stdout.write(chunk)
133
+ }
134
+
135
+ process.stdout.write('\n')
136
+
137
+ // Output:
138
+ // name,age,city
139
+ // Alice,30,New York
140
+ // Bob,25,Los Angeles
141
+ // Charlie,35,Chicago
142
+ ```
143
+
144
+ ## Transforming parsed CSV data before further processing
145
+
146
+ ```typescript
147
+ import { Csv } from 'csv-stream-lite'
148
+
149
+ const csvData = `first_name,last_name,age
150
+ Alice,Smith,30
151
+ Bob,Johnson,25`
152
+
153
+ const csvStream = new Csv(csvData, {
154
+ transform: (record: {
155
+ first_name: string
156
+ last_name: string
157
+ age: string
158
+ }) => ({
159
+ fullName: `${record.first_name} ${record.last_name}`,
160
+ age: Number(record.age),
161
+ }),
162
+ })
163
+
164
+ for await (const row of csvStream.streamObjectsAsync()) {
165
+ console.log(row)
166
+ }
167
+
168
+ // Output:
169
+ // { fullName: 'Alice Smith', age: 30 }
170
+ // { fullName: 'Bob Johnson', age: 25 }
171
+ ```
172
+
173
+ ## Transform CSV data during stringification
174
+
175
+ ```typescript
176
+ import { Csv } from 'csv-stream-lite'
177
+
178
+ const data = [
179
+ { name: 'Alice', age: 30, city: 'New York' },
180
+ { name: 'Bob', age: 25, city: 'Los Angeles' },
181
+ ]
182
+
183
+ const csvStringifier = Csv.stringifier(data, {
184
+ transform: (row) => ({
185
+ fullName: row.name.toUpperCase(),
186
+ ageInFiveYears: row.age + 5,
187
+ city: row.city,
188
+ cityWithCountry: `${row.city}, USA`,
189
+ }),
190
+ })
191
+
192
+ console.log(csvStringifier.toString())
193
+ // Expected output:
194
+ // fullName,ageInFiveYears,city,cityWithCountry
195
+ // ALICE,35,New York,"New York, USA"
196
+ // BOB,30,Los Angeles,"Los Angeles, USA"
197
+ ```
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Jacob Shirley
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,273 @@
1
+ **[Examples](./EXAMPLES.md)** | **[Documentation](https://jacobshirley.github.io/csv-stream-lite/v1)**
2
+
3
+ # csv-stream-lite
4
+
5
+ A lightweight, memory-efficient, zero-dependency streaming CSV parser and stringifier written in TypeScript. Process large CSV files without loading them entirely into memory.
6
+
7
+ [![npm version](https://img.shields.io/npm/v/csv-stream-lite.svg)](https://www.npmjs.com/package/csv-stream-lite)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
+
10
+ ## Features
11
+
12
+ - 🚀 **Zero Dependencies** - No external dependencies
13
+ - 💪 **TypeScript First** - Written in TypeScript with full type safety
14
+ - 🌊 **Streaming Support** - Process large CSV files without loading them into memory
15
+ - ⚡ **High Performance** - Efficient byte-level parsing
16
+ - 🔄 **Dual Mode** - Both synchronous and asynchronous APIs
17
+ - 🌐 **Universal** - Works in Node.js and browser environments
18
+ - 📝 **Flexible** - Support for custom delimiters, escape characters, and transformers
19
+ - ✅ **Well Tested** - Comprehensive test coverage
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ npm install csv-stream-lite
25
+ ```
26
+
27
+ ```bash
28
+ yarn add csv-stream-lite
29
+ ```
30
+
31
+ ```bash
32
+ pnpm add csv-stream-lite
33
+ ```
34
+
35
+ ## Quick Start
36
+
37
+ ### Parsing CSV
38
+
39
+ ```typescript
40
+ import { Csv } from 'csv-stream-lite'
41
+
42
+ // Parse CSV string
43
+ const csvData = `name,age,city
44
+ Alice,30,New York
45
+ Bob,25,Los Angeles`
46
+
47
+ const csv = new Csv(csvData, { readHeaders: true })
48
+
49
+ // Sync streaming
50
+ for (const row of csv.streamObjects()) {
51
+ console.log(row) // { name: 'Alice', age: '30', city: 'New York' }
52
+ }
53
+
54
+ // Async streaming (for large files)
55
+ for await (const row of csv.streamObjectsAsync()) {
56
+ console.log(row)
57
+ }
58
+ ```
59
+
60
+ ### Parsing with Type Transformers
61
+
62
+ ```typescript
63
+ import { Csv, CsvObjectShape } from 'csv-stream-lite'
64
+
65
+ interface User {
66
+ name: string
67
+ age: number
68
+ active: boolean
69
+ }
70
+
71
+ const shape: CsvObjectShape<User> = {
72
+ name: String,
73
+ age: Number,
74
+ active: Boolean,
75
+ }
76
+
77
+ const csv = new Csv<User>(fileStream, { shape })
78
+
79
+ for await (const user of csv.streamObjectsAsync()) {
80
+ console.log(user.age) // Typed as number
81
+ }
82
+ ```
83
+
84
+ ### Stringifying to CSV
85
+
86
+ ```typescript
87
+ import { Csv } from 'csv-stream-lite'
88
+
89
+ const data = [
90
+ { name: 'Alice', age: 30, city: 'New York' },
91
+ { name: 'Bob', age: 25, city: 'Los Angeles' },
92
+ ]
93
+
94
+ // Sync
95
+ for (const chunk of Csv.stringify(data, { headers: ['name', 'age', 'city'] })) {
96
+ process.stdout.write(chunk)
97
+ }
98
+
99
+ // Async
100
+ for await (const chunk of Csv.stringifyAsync(data)) {
101
+ process.stdout.write(chunk)
102
+ }
103
+
104
+ // Or get complete string
105
+ const csvString = new CsvStringify(data).toString()
106
+ ```
107
+
108
+ ## API Documentation
109
+
110
+ Full API documentation is available at [https://jacobshirley.github.io/csv-stream-lite/v1](https://jacobshirley.github.io/csv-stream-lite/v1)
111
+
112
+ ## Advanced Usage
113
+
114
+ ### Reading from File Stream (Node.js)
115
+
116
+ ```typescript
117
+ import { createReadStream } from 'fs'
118
+ import { Csv } from 'csv-stream-lite'
119
+
120
+ const fileStream = createReadStream('large-file.csv')
121
+
122
+ const csv = new Csv(fileStream, { readHeaders: true })
123
+
124
+ for await (const row of csv.streamObjectsAsync()) {
125
+ // Process each row without loading entire file into memory
126
+ console.log(row)
127
+ }
128
+ ```
129
+
130
+ ### Custom Delimiters
131
+
132
+ ```typescript
133
+ // Tab-separated values
134
+ const csv = new Csv(tsvData, {
135
+ separator: '\t',
136
+ readHeaders: true,
137
+ })
138
+
139
+ // Semicolon-separated values
140
+ const csv = new Csv(csvData, {
141
+ separator: ';',
142
+ readHeaders: true,
143
+ })
144
+ ```
145
+
146
+ ### Strict Column Validation
147
+
148
+ ```typescript
149
+ import { Csv, TooManyColumnsError, TooFewColumnsError } from 'csv-stream-lite'
150
+
151
+ const csvData = `name,age,city
152
+ Alice,30,New York
153
+ Bob,25,Los Angeles,ExtraColumn`
154
+
155
+ const csv = new Csv(csvData, {
156
+ headers: ['name', 'age', 'city'],
157
+ strictColumns: true, // Throws error if column count doesn't match
158
+ })
159
+
160
+ try {
161
+ for await (const row of csv.streamObjectsAsync()) {
162
+ console.log(row)
163
+ }
164
+ } catch (error) {
165
+ if (error instanceof TooManyColumnsError) {
166
+ console.error('Row has too many columns')
167
+ } else if (error instanceof TooFewColumnsError) {
168
+ console.error('Row has too few columns')
169
+ }
170
+ }
171
+ ```
172
+
173
+ ### Row Transformation
174
+
175
+ ```typescript
176
+ const csv = new Csv(csvData, {
177
+ readHeaders: true,
178
+ transform: (row) => ({
179
+ ...row,
180
+ fullName: `${row.firstName} ${row.lastName}`,
181
+ age: Number(row.age),
182
+ }),
183
+ })
184
+ ```
185
+
186
+ ### Writing to File Stream (Node.js)
187
+
188
+ ```typescript
189
+ import { createWriteStream } from 'fs'
190
+ import { CsvStringify } from 'csv-stream-lite'
191
+
192
+ const writeStream = createWriteStream('output.csv')
193
+
194
+ const data = [
195
+ { name: 'Alice', age: 30 },
196
+ { name: 'Bob', age: 25 },
197
+ ]
198
+
199
+ const stringifier = new CsvStringify(data, {
200
+ headers: ['name', 'age'],
201
+ })
202
+
203
+ for await (const chunk of stringifier) {
204
+ writeStream.write(chunk)
205
+ }
206
+
207
+ writeStream.end()
208
+ ```
209
+
210
+ ## Error Handling
211
+
212
+ The library provides specific error types for different scenarios:
213
+
214
+ - `CsvStreamLiteError` - Base error class
215
+ - `NoMoreTokensError` - Buffer is empty and more input is needed
216
+ - `EofReachedError` - End of file reached
217
+ - `BufferSizeExceededError` - Buffer size limit exceeded
218
+ - `TooManyColumnsError` - Row has more columns than expected (when `strictColumns: true`)
219
+ - `TooFewColumnsError` - Row has fewer columns than expected (when `strictColumns: true`)
220
+
221
+ ## Performance
222
+
223
+ csv-stream-lite is designed for memory efficiency and high performance:
224
+
225
+ - **Streaming Architecture**: Process files of any size with constant memory usage
226
+ - **Lazy Evaluation**: Data is only parsed as it's consumed
227
+ - **Byte-Level Parsing**: Efficient low-level parsing without intermediate string allocations
228
+ - **Chunked Processing**: Configurable chunk sizes for optimal performance
229
+
230
+ ## TypeScript Support
231
+
232
+ Full TypeScript support with comprehensive type definitions:
233
+
234
+ ```typescript
235
+ import { Csv, CsvObjectShape } from 'csv-stream-lite'
236
+
237
+ interface User {
238
+ id: number
239
+ name: string
240
+ email: string
241
+ active: boolean
242
+ }
243
+
244
+ const shape: CsvObjectShape<User> = {
245
+ id: Number,
246
+ name: String,
247
+ email: String,
248
+ active: Boolean,
249
+ }
250
+
251
+ const csv = new Csv<User>(data, { shape })
252
+
253
+ // Type-safe iteration
254
+ for await (const user of csv.streamObjectsAsync()) {
255
+ console.log(user.id) // TypeScript knows this is a number
256
+ }
257
+ ```
258
+
259
+ ## Browser Support
260
+
261
+ csv-stream-lite works in modern browsers with support for:
262
+
263
+ - `ReadableStream` API
264
+ - `AsyncIterable` protocol
265
+ - ES2018+ features
266
+
267
+ ## Contributing
268
+
269
+ Contributions are welcome! Please read our [Contributing Guide](./CONTRIBUTING.md) for details.
270
+
271
+ ## License
272
+
273
+ MIT © [Jacob Shirley](https://github.com/jacobshirley)
@@ -0,0 +1,112 @@
1
+ import type { ByteStream, StreamInput } from './types.js';
2
+ /**
3
+ * A buffer for managing byte-level input with support for async streams.
4
+ * Provides lookahead, consumption tracking, and buffer compaction capabilities.
5
+ */
6
+ export declare class ByteBuffer {
7
+ /** Maximum size of the buffer before compaction */
8
+ maxBufferSize: number;
9
+ /** Whether end of file has been signaled */
10
+ eof: boolean;
11
+ /** Current position in the buffer */
12
+ bufferIndex: number;
13
+ /** Whether the buffer is locked against compaction */
14
+ locked: boolean;
15
+ /** Whether to allow exceeding the buffer size temporarily */
16
+ allowBufferToBeExceeded: boolean;
17
+ /** Current position in the input stream */
18
+ protected inputOffset: number;
19
+ /** Number of outputs generated */
20
+ protected outputOffset: number;
21
+ /** Buffer holding input items */
22
+ protected buffer: number[];
23
+ /** Optional async iterable input source */
24
+ protected asyncIterable?: ByteStream;
25
+ /**
26
+ * Creates a new ByteBuffer instance.
27
+ *
28
+ * @param asyncIterable - Optional async iterable source for streaming input
29
+ */
30
+ constructor(asyncIterable?: ByteStream);
31
+ /**
32
+ * Reads data from the stream into the buffer.
33
+ * Reads up to maxBufferSize bytes at a time.
34
+ */
35
+ readStream(): boolean;
36
+ /**
37
+ * Reads data from the sync or async stream into the buffer.
38
+ * Reads up to maxBufferSize bytes at a time.
39
+ */
40
+ readStreamAsync(): Promise<void>;
41
+ /**
42
+ * Gets the current length of the buffer.
43
+ *
44
+ * @returns The number of bytes in the buffer
45
+ */
46
+ get length(): number;
47
+ /**
48
+ * Feeds input items into the parser buffer.
49
+ *
50
+ * @param input - Input items to add to the buffer
51
+ */
52
+ feed(...input: StreamInput[]): void;
53
+ push(byte: number): void;
54
+ /**
55
+ * Checks if end of file has been reached and buffer is exhausted.
56
+ *
57
+ * @returns True if no more input is available
58
+ */
59
+ atEof(): boolean;
60
+ /**
61
+ * Peeks at an item in the buffer without consuming it.
62
+ *
63
+ * @param ahead - Number of positions to look ahead (default: 0)
64
+ * @returns The item at the peek position, or null if at EOF
65
+ * @throws NoMoreTokensError if more input is needed and EOF not signaled
66
+ */
67
+ peek(ahead?: number): number | null;
68
+ /**
69
+ * Consumes and returns the next item from the buffer.
70
+ *
71
+ * @returns The next item
72
+ * @throws NoMoreTokensError if more input is needed and EOF not signaled
73
+ * @throws EofReachedError if at EOF and no more items available
74
+ */
75
+ next(): number;
76
+ /**
77
+ * Consumes and validates the next item against an expected type or value.
78
+ *
79
+ * @typeParam T - The expected item type
80
+ * @param itemType - Constructor or value to match against
81
+ * @returns The consumed item cast to the expected type
82
+ * @throws Error if the item doesn't match the expected type/value
83
+ */
84
+ expect<T extends number>(itemType: T): T;
85
+ /**
86
+ * Compacts the buffer by removing consumed items
87
+ */
88
+ compact(): void;
89
+ /**
90
+ * Override to customize when to compact the buffer
91
+ * By default, compacts when more than maxBufferSize bytes have been consumed
92
+ *
93
+ * @returns boolean indicating whether to compact the buffer
94
+ */
95
+ canCompact(): boolean;
96
+ /**
97
+ * Attempts to execute a function, resetting buffer position on failure.
98
+ * Useful for speculative parsing attempts that may need to be retried.
99
+ *
100
+ * @typeParam T - The return type of the try function
101
+ * @param tryFn - Function to attempt execution
102
+ * @param onFail - Optional callback invoked on failure
103
+ * @returns The result of tryFn if successful, undefined if NoMoreTokensError thrown
104
+ */
105
+ resetOnFail<T>(tryFn: () => T, onFail?: (e: Error) => void): T | undefined;
106
+ /**
107
+ * Returns a string representation of the buffer state for debugging.
108
+ *
109
+ * @returns A formatted string showing buffer contents and state
110
+ */
111
+ toString(): string;
112
+ }