Ardis Lu [Fri, 5 Sep 2025 06:10:17 +0000 (23:10 -0700)]
Pin Node.js and npm versions in `devEngines`
`devEngines` is the standardized field for this purpose [0], with varying levels of tooling support. The duplicative and non-standard `.node-version` file will eventually be removed once tooling support for `devEngines` is more widespread.
This change enables users to create a `TransformStream` by doing `new CSVWriter()` instead of always creating an ultimate sink `WritableStream`. This behavior is useful when there is some transformation that must be performed on the CSV data before writing (e.g., compressing into a `.csv.gz` file using `CompressionStream`).
Ardis Lu [Wed, 6 Aug 2025 04:38:51 +0000 (21:38 -0700)]
Fix bug where it was not possible to read into an ArrayBuffer with offset
The code was originally correct because it was copied from the Streams specification [1], but turned incorrect after copying from Node.js's implementation [2], and now reverted back to the original correct implementation.
Ardis Lu [Tue, 5 Aug 2025 06:50:42 +0000 (23:50 -0700)]
Delete unnecessary negative test
The row length check in `csvStreamEqualWritable` now covers the original testing edge case that necessitated the negative test. Now the test will correctly fail without requiring the negative test to trigger the failure.
Ardis Lu [Mon, 4 Aug 2025 06:06:34 +0000 (23:06 -0700)]
Remove intermediate JSON serialization and `rawInput`/`rawOutput` options
Previously, each row was JSON serialized and deserialized on each `.pipeThrough`. The intent of this serialization was to normalize the data of each row, with an escape hatch in the `rawInput` and `rawOutput` options.
However, this approach is counterintuitive because most users would expect a CSV row to simply be an `Array<string>`, NOT a `string` that can be `JSON.parse`-ed into an `Array<string>`. Furthermore, this excessive serialization has a significant performance impact. Lastly, the `rawInput` and `rawOutput` options effectively made `CSVTransformer` an overly complex and difficult-to-use JSON parser on top of a CSV transformer.
So, simply removing the JSON serialization and directly enqueuing the actual object (i.e., the `Array<string>`) resolves all these issues with minimal impact to the user-facing API.
This change enables users to create a `TransformStream` by doing `new CSVReader()` instead of always creating an underlying source `ReadableStream`. This behavior is useful when there is some transformation that must be performed on the source file before parsing (e.g., decompressing a `.csv.gz` file using `DecompressionStream`).
Ardis Lu [Tue, 29 Jul 2025 06:35:40 +0000 (23:35 -0700)]
Add tests for `instanceof` behavior
In preparation for possible future refactoring for these classes to return a `Proxy` object (this pattern is required to simulate dynamic superclasses, e.g., `CSVReader` returning either `ReadableStream` OR `TransformStream` depending on constructor argument). If such refactoring takes place, these tests will check that `Proxy` was implemented correctly and maintain backwards compatibility.
Ardis Lu [Tue, 15 Jul 2025 05:07:56 +0000 (22:07 -0700)]
Add initial tests for memory leaks
Future tests should be dedicated for finding memory leaks only, but as an initial step it makes sense to add a simple sanity check to the large file tests.
Ardis Lu [Sat, 12 Jul 2025 06:22:50 +0000 (23:22 -0700)]
Re-export callback types
These types were initially missed because they are defined with the `callback` annotation and not `typedef`. For future completeness, search for `export type` in the generated declaration files.
Ardis Lu [Sat, 12 Jul 2025 05:09:25 +0000 (22:09 -0700)]
Add `useLiteralNames` option to disable column name normalization
This behavior is useful to preserve data when an input CSV has multiple columns with the same normalized name. By default, `CSVNormalizer` will remove these duplicate columns (the data from the last column is used).
Note that columns with the same *literal* name are still removed even when `useLiteralNames` is `true`. If you need to preserve columns with the same literal name, use a lower-level transformation (e.g., `CSVTransformer`).
Ardis Lu [Fri, 11 Jul 2025 04:49:00 +0000 (21:49 -0700)]
Re-export all types in `index.js`
Currently, the types are accessible internally in the library and within `index.js`, but NOT accessible from consumers importing from the `index.js` file. A re-export is required to make the types accessible to end-users.
Additionally, the JSDoc import will only work for types declared at the module scope, so `CSVWriterStatus` was moved to the outer scope.
Ardis Lu [Thu, 10 Jul 2025 05:10:20 +0000 (22:10 -0700)]
Ignore `handleHeaders` if the row has been normalized
This behavior is desirable when `CSVTransformer` interacts with `CSVNormalizer` or any other transformation which converts the row from `Array<string>` into `Array<Object>`, because it avoids the need to pass `handleHeaders: true` to `CSVTransformer` (which is counterintuitive). It is more accurate to say the header row has been deleted by the transformation and must by re-inserted into the write stream later.
The loosening of the type checking is not a breaking change. Since the row is passed directly to `JSON.parse()`, the fields could have always been type `Object` instead of `string`.
Ardis Lu [Thu, 10 Jul 2025 04:33:12 +0000 (21:33 -0700)]
Fall back on `name` if `displayName` is undefined
This behavior is useful when passing rows to `CSVDenormalizer` after a transformation *other* than `CSVNormalizer` (which automatically creates `displayName`).
Ardis Lu [Thu, 10 Jul 2025 04:12:44 +0000 (21:12 -0700)]
Set default normalization type to `'string'`
This behavior is helpful to perform minimal normalization tasks but otherwise preserve the default CSV transformation behavior (i.e., have all input values be a string).
This is not a breaking change. Previously, `type` would also default to `'string'` after logging a warning in console.
Ardis Lu [Fri, 4 Jul 2025 05:02:25 +0000 (22:02 -0700)]
Increase `highWaterMark` for `CSVWriter`'s queuing strategy
This change improves the 100MB Write benchmark from ~9s to ~7s.
Bumping `highWaterMark` to >1 also changes the stream's interaction with `AbortController`, so now the chunk processed during an abort is included in the write.
I believe this behavior has to do with the task queue order of the callback for the `AbortSignal`'s `abort` event - so now with multiple chunks in the queue, the last chunk is written before the `abort` event is processed. I was unable to force this writing code to occur after the `abort` event in a deterministic way.
This situation is a rare edge case so letting it through.
Ardis Lu [Fri, 4 Jul 2025 03:45:55 +0000 (20:45 -0700)]
Simplify and harden tick indicator
Relying on physical timing is flaky (e.g., it will fail if executing on a slow machine). Sending a simple `tick` increment into the task queue is more reliable.
Ardis Lu [Tue, 1 Jul 2025 04:19:16 +0000 (21:19 -0700)]
Increase default chunk size from 1 KiB to 64 KiB
Benchmarks showed improvements or no change across all tests. Notably, 100MB Read improved from ~13s to ~3s.
The magic value of 64 KiB was selected based on Node.js's default of 16 KiB in similar code [1] and manually testing some multiples over that (Node.js tends to be conservative).
Ardis Lu [Wed, 4 Jun 2025 06:45:32 +0000 (23:45 -0700)]
Simplify release notes to be auto generated
Reasons in favor of minimal release notes: (1) Releases is a GitHub functionality, it's better not to invest too much time into vendor-specific configurations. (2) There is no binary associated with this library so Releases has reduced significance. (3) Meaningful release notes are still contained in git tag annotations, which can be carried across hosts.
One downside is losing meaningful messages in email notifications if people subscribe to follow releases on GitHub.
Ardis Lu [Tue, 27 May 2025 05:01:57 +0000 (22:01 -0700)]
Refactor to pass options through to private fields
This change makes the code consistent with previous refactoring on the core code, and also prevents unexpected behavior from mutating the options object after instantiation.
Ardis Lu [Sun, 25 May 2025 04:55:24 +0000 (21:55 -0700)]
Explicitly import `ReadableStream` to make TS understand `ReadableStream.from`
This change does not affect the code execution, but it makes the TS language server understand the `ReadableStream.from` static function. Without the explicit import, it is not possible using only JSDoc to merge the `ReadableStream` instance interface with the `ReadableStream` constructor because they share the same name.
Ardis Lu [Sun, 25 May 2025 04:24:08 +0000 (21:24 -0700)]
Migrate from CLI options to explicit `tsconfig.json`
This change makes it easier to use an explicit `lib` configuration and types, which resolves type issues resulting from subtle mismatches between implied type imports (e.g., some files importing `ReadableStream` from TS's built-in Web API types and others importing from Node.js's `streams/web`).
This is the cleanest way to make a JS library importable to a TS project. This approach is similar to Svelte's approach.
Note the dev dependency on TS is only used for `.d.ts` generation. The source code itself still does not use TS (or any dependencies other than Node.js) at all.