Ardis Lu [Thu, 13 Nov 2025 07:15:55 +0000 (23:15 -0800)]
Loosen type definition to `AsyncIterable`
This change reflects the actual runtime check that is being performed (i.e., that `Symbol.asyncIterator` is implemented) and allows users to pass (runtime-valid) `async function*` transformation functions without any type errors.
Ardis Lu [Thu, 30 Oct 2025 06:46:01 +0000 (23:46 -0700)]
Add `.from` static method
This method is intended to be the inverse of the `.toObject` method. It is useful to have such a method as a functional (i.e., non-mutating) alternative to `.toFieldMap` to transform rows in a transformation function.
Ardis Lu [Sun, 19 Oct 2025 04:30:41 +0000 (21:30 -0700)]
Implement `.delete()` and `.clear()`
Useful when a user wants to mostly duplicate an input row but make some minor tweaks. Not having the capability to remove rows leads to unwieldy workarounds.
Ardis Lu [Mon, 6 Oct 2025 06:00:41 +0000 (23:00 -0700)]
Don't pin versions in `devEngines`
This change is required to make the release CI work. The mismatched `npm` version in `devEngines` blocks the `npm i -g npm`, so it is impossible for `npm` to update itself.
Can revert this change only after Node.js version managers start using `devEngines` to automatically set `node` and `npm` versions.
Ardis Lu [Mon, 6 Oct 2025 04:42:54 +0000 (21:42 -0700)]
Stringify all values passed through
This change makes `CSVDenormalizer` behave consistently with its current type definition and aligns expected behavior (if `CSVNormalizer` takes strings and gives native objects, then `CSVDenormalizer` should take native objects and give strings).
Ardis Lu [Fri, 26 Sep 2025 07:00:09 +0000 (00:00 -0700)]
Pass through native type whenever possible
The stringification was legacy code from the original design of always JSON serializing each row between every stream. Since that serialization is no longer done, `CSVNormalizer` can just pass the native data type through. The downstream `CSVDenormalizer` should stringify all the data if the old behavior is desired.
Ardis Lu [Fri, 26 Sep 2025 06:45:57 +0000 (23:45 -0700)]
Remove empty rows before the header row
Empty rows before the header are sometimes used for formatting purposes, and they should be removed from the data.
It is not possible to normalize an empty row before the headers are known, so empty rows that come before the header row are *always* removed, even if `passthroughEmptyRows` is `true`.
Ardis Lu [Thu, 25 Sep 2025 06:33:37 +0000 (23:33 -0700)]
Make column matching case insensitive by default
Before: `datetime` and `dateTime` are considered different columns.
After: `datetime` and `dateTime` are considered the same column (last column prevails).
Note the output column name will still use the same casing passed in `CSVNormalizerHeader`.
`CSVNormalizer` already significantly modifies column names by normalizing to camelCase, so it is surprising the column matching is still case sensitive. It is likely that in the case a user wants `column A` and `columnA` to be the same column, that they'd also want `columna` to be the same as well.
Ardis Lu [Thu, 25 Sep 2025 06:00:11 +0000 (23:00 -0700)]
Parse Unix timestamps
These three ways of representing dates as a number each have distinct magnitudes. I picked arbitrary cutoff dates to switch the logic under the assumption that values commonly seen in CSVs would not cross those cutoff dates.
Ardis Lu [Sun, 21 Sep 2025 05:28:59 +0000 (22:28 -0700)]
Properly isolate and perform memory leak test
Previously the garbage collection tests were flaky, because `gc()` may or may not happen before the memory leak threshold is tested.
Now the test will properly block on garbage collection (observed to take about 15 seconds). Separating it into its own test also makes it easier to debug the `large:` test suites by reducing concerns in each test.
Ardis Lu [Thu, 18 Sep 2025 06:47:10 +0000 (23:47 -0700)]
Allow passing an array to `placeholder`
This functionality is helpful to indicate different and more useful placeholders for each column.
It also allows setting a completely blank row (without any commas) on error, which may be desirable to make output CSV row counts match input CSV row counts without clutter.
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.