From 980d94413bc7ef9d3a1aa5535edabf26f0f3c678 Mon Sep 17 00:00:00 2001 From: David Worms Date: Wed, 13 May 2026 09:49:20 +0200 Subject: [PATCH 1/4] refactor(csv-stringify): remove unused variable --- packages/csv-stringify/lib/api/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/csv-stringify/lib/api/index.js b/packages/csv-stringify/lib/api/index.js index 392cf7fa..44abd1be 100644 --- a/packages/csv-stringify/lib/api/index.js +++ b/packages/csv-stringify/lib/api/index.js @@ -121,6 +121,7 @@ const stringifier = function (options, state, info) { if (typeof value === "string") { options = this.options; } else if (is_object(value)) { + // Value is considerered as a mix of a value and options options = value; value = options.value; delete options.value; @@ -136,6 +137,7 @@ const stringifier = function (options, state, info) { ), ]; } + // Merge global options with the ones returned by cast options = { ...this.options, ...options }; [err, options] = normalize_options(options); if (err !== undefined) { @@ -304,7 +306,7 @@ const stringifier = function (options, state, info) { } else if (type === "object" && value !== null) { return [undefined, this.options.cast.object(value, context)]; } else { - return [undefined, value, value]; + return [undefined, value]; } } catch (err) { return [err]; From 6b59bbd928c7604bb17763d2cdf6c28415da979e Mon Sep 17 00:00:00 2001 From: Vishwak Thatikonda Date: Tue, 12 May 2026 16:21:52 -0700 Subject: [PATCH 2/4] feat: support null and undefined in stringify cast options --- packages/csv-stringify/lib/api/index.js | 7 ++ packages/csv-stringify/lib/index.d.ts | 4 + .../csv-stringify/test/option.cast.null.ts | 110 ++++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 packages/csv-stringify/test/option.cast.null.ts diff --git a/packages/csv-stringify/lib/api/index.js b/packages/csv-stringify/lib/api/index.js index 44abd1be..0f214501 100644 --- a/packages/csv-stringify/lib/api/index.js +++ b/packages/csv-stringify/lib/api/index.js @@ -305,6 +305,13 @@ const stringifier = function (options, state, info) { return [undefined, this.options.cast.date(value, context)]; } else if (type === "object" && value !== null) { return [undefined, this.options.cast.object(value, context)]; + } else if (value === null && this.options.cast.null !== undefined) { + return [undefined, this.options.cast.null(value, context)]; + } else if ( + value === undefined && + this.options.cast.undefined !== undefined + ) { + return [undefined, this.options.cast.undefined(value, context)]; } else { return [undefined, value]; } diff --git a/packages/csv-stringify/lib/index.d.ts b/packages/csv-stringify/lib/index.d.ts index 76b53514..f93ba897 100644 --- a/packages/csv-stringify/lib/index.d.ts +++ b/packages/csv-stringify/lib/index.d.ts @@ -60,6 +60,8 @@ export interface OptionsNormalized extends stream.TransformOptions { */ object?: Cast>; string?: Cast; + null?: Cast; + undefined?: Cast; }; /** * List of fields, applied when `transform` returns an object, order matters, @@ -140,6 +142,8 @@ export interface Options extends stream.TransformOptions { */ object?: Cast>; string?: Cast; + null?: Cast; + undefined?: Cast; }; /** * List of fields, applied when `transform` returns an object, order matters, diff --git a/packages/csv-stringify/test/option.cast.null.ts b/packages/csv-stringify/test/option.cast.null.ts new file mode 100644 index 00000000..4671eccd --- /dev/null +++ b/packages/csv-stringify/test/option.cast.null.ts @@ -0,0 +1,110 @@ +import "should"; +import dedent from "dedent"; +import { stringify } from "../lib/index.js"; + +describe("Option `cast` with null and undefined", function () { + it("cast.null can emit unquoted NULL", function (next) { + stringify( + [ + { a: "foo", b: null, c: "bar" } + ], + { + header: true, + cast: { + null: function (value, context) { + return { value: "NULL", quoted: false }; + } + } + }, + (err, data) => { + if (!err) { + data.should.eql("a,b,c\nfoo,NULL,bar\n"); + } + next(err); + } + ); + }); + + it("cast.undefined can emit unquoted NULL", function (next) { + stringify( + [ + { a: "foo", b: undefined, c: "bar" } + ], + { + header: true, + cast: { + undefined: function (value, context) { + return { value: "NULL", quoted: false }; + } + } + }, + (err, data) => { + if (!err) { + data.should.eql("a,b,c\nfoo,NULL,bar\n"); + } + next(err); + } + ); + }); + + it("cast.null receives expected context", function (next) { + stringify( + [ + [null] + ], + { + cast: { + null: function (value, context) { + Object.keys(context) + .sort() + .should.eql(["column", "header", "index", "records"]); + return ""; + } + } + }, + next + ); + }); + + it("cast.null returning a string respects quoting rules", function (next) { + stringify( + [ + { a: "foo", b: null, c: "bar" } + ], + { + quoted: true, + cast: { + null: function (value, context) { + return "NULL"; + } + } + }, + (err, data) => { + if (!err) { + data.should.eql('"foo","NULL","bar"\n'); + } + next(err); + } + ); + }); + + it("cast.null catches error and surfaces it", function (next) { + stringify( + [ + [null] + ], + { + cast: { + null: function () { + throw Error("Catchme"); + } + } + }, + (err) => { + if (!err) return next(Error("Invalid assessment")); + err.message.should.eql("Catchme"); + next(); + } + ); + }); +}); From eabdf77b5d596d8344465dfa10fdebbd25df27ea Mon Sep 17 00:00:00 2001 From: David Worms Date: Wed, 13 May 2026 09:53:53 +0200 Subject: [PATCH 3/4] refactor(csv-stringify): remove eslint warnings --- .../csv-stringify/test/option.cast.null.ts | 57 ++++++++----------- 1 file changed, 23 insertions(+), 34 deletions(-) diff --git a/packages/csv-stringify/test/option.cast.null.ts b/packages/csv-stringify/test/option.cast.null.ts index 4671eccd..9a218a1f 100644 --- a/packages/csv-stringify/test/option.cast.null.ts +++ b/packages/csv-stringify/test/option.cast.null.ts @@ -1,57 +1,50 @@ import "should"; -import dedent from "dedent"; import { stringify } from "../lib/index.js"; describe("Option `cast` with null and undefined", function () { it("cast.null can emit unquoted NULL", function (next) { stringify( - [ - { a: "foo", b: null, c: "bar" } - ], + [{ a: "foo", b: null, c: "bar" }], { header: true, cast: { - null: function (value, context) { + null: function () { return { value: "NULL", quoted: false }; - } - } + }, + }, }, (err, data) => { if (!err) { data.should.eql("a,b,c\nfoo,NULL,bar\n"); } next(err); - } + }, ); }); it("cast.undefined can emit unquoted NULL", function (next) { stringify( - [ - { a: "foo", b: undefined, c: "bar" } - ], + [{ a: "foo", b: undefined, c: "bar" }], { header: true, cast: { - undefined: function (value, context) { + undefined: function () { return { value: "NULL", quoted: false }; - } - } + }, + }, }, (err, data) => { if (!err) { data.should.eql("a,b,c\nfoo,NULL,bar\n"); } next(err); - } + }, ); }); it("cast.null receives expected context", function (next) { stringify( - [ - [null] - ], + [[null]], { cast: { null: function (value, context) { @@ -59,52 +52,48 @@ describe("Option `cast` with null and undefined", function () { .sort() .should.eql(["column", "header", "index", "records"]); return ""; - } - } + }, + }, }, - next + next, ); }); it("cast.null returning a string respects quoting rules", function (next) { stringify( - [ - { a: "foo", b: null, c: "bar" } - ], + [{ a: "foo", b: null, c: "bar" }], { quoted: true, cast: { - null: function (value, context) { + null: function () { return "NULL"; - } - } + }, + }, }, (err, data) => { if (!err) { data.should.eql('"foo","NULL","bar"\n'); } next(err); - } + }, ); }); it("cast.null catches error and surfaces it", function (next) { stringify( - [ - [null] - ], + [[null]], { cast: { null: function () { throw Error("Catchme"); - } - } + }, + }, }, (err) => { if (!err) return next(Error("Invalid assessment")); err.message.should.eql("Catchme"); next(); - } + }, ); }); }); From 6c51f2b52f6f70a113e4944fe141ab64fd210633 Mon Sep 17 00:00:00 2001 From: David Worms Date: Wed, 13 May 2026 11:11:47 +0200 Subject: [PATCH 4/4] refactor(csv-stringify): cast types isolation --- packages/csv-stringify/test/option.cast.ts | 90 ------------- ...tion.cast.null.ts => option.cast.types.ts} | 118 +++++++++++++++--- 2 files changed, 104 insertions(+), 104 deletions(-) rename packages/csv-stringify/test/{option.cast.null.ts => option.cast.types.ts} (55%) diff --git a/packages/csv-stringify/test/option.cast.ts b/packages/csv-stringify/test/option.cast.ts index f966eb77..3a17d49d 100644 --- a/packages/csv-stringify/test/option.cast.ts +++ b/packages/csv-stringify/test/option.cast.ts @@ -20,96 +20,6 @@ describe("Option `cast`", function () { }); describe("udf", function () { - it("handle string formatter", function (next) { - stringify( - [ - { - value: "ok", - }, - ], - { cast: { string: () => "X" } }, - (err, data) => { - if (!err) data.should.eql("X\n"); - next(err); - }, - ); - }); - - it("handle boolean formatter", function (next) { - stringify( - [ - { - value: true, - }, - ], - { cast: { boolean: () => "X" } }, - (err, data) => { - if (!err) data.should.eql("X\n"); - next(err); - }, - ); - }); - - it("handle date formatter", function (next) { - stringify( - [ - { - value: new Date(), - }, - ], - { cast: { date: () => "X" } }, - (err, data) => { - if (!err) data.should.eql("X\n"); - next(err); - }, - ); - }); - - it("handle number formatter", function (next) { - stringify( - [ - { - value: 3.14, - }, - ], - { cast: { number: (value) => "" + value * 2 } }, - (err, data) => { - if (!err) data.should.eql("6.28\n"); - next(err); - }, - ); - }); - - it("handle bigint formatter", function (next) { - stringify( - [ - { - value: BigInt(9007199254740991), - }, - ], - { cast: { bigint: (value) => "" + value / BigInt(2) } }, - (err, data) => { - if (!err) data.should.eql("4503599627370495\n"); - next(err); - }, - ); - }); - - it("handle object formatter", function (next) { - stringify( - [ - { - value: { a: 1 }, - }, - ], - { cast: { object: () => "X" } }, - (err, data) => { - if (!err) data.should.eql("X\n"); - next(err); - }, - ); - }); - it("catch error", function (next) { stringify( [ diff --git a/packages/csv-stringify/test/option.cast.null.ts b/packages/csv-stringify/test/option.cast.types.ts similarity index 55% rename from packages/csv-stringify/test/option.cast.null.ts rename to packages/csv-stringify/test/option.cast.types.ts index 9a218a1f..d87b71e3 100644 --- a/packages/csv-stringify/test/option.cast.null.ts +++ b/packages/csv-stringify/test/option.cast.types.ts @@ -2,33 +2,58 @@ import "should"; import { stringify } from "../lib/index.js"; describe("Option `cast` with null and undefined", function () { - it("cast.null can emit unquoted NULL", function (next) { + it("cast.bigint formatter", function (next) { stringify( - [{ a: "foo", b: null, c: "bar" }], - { - header: true, - cast: { - null: function () { - return { value: "NULL", quoted: false }; - }, + [ + { + value: BigInt(9007199254740991), }, + ], + { cast: { bigint: (value) => "" + value / BigInt(2) } }, + (err, data) => { + if (!err) data.should.eql("4503599627370495\n"); + next(err); }, + ); + }); + + it("cast.boolean formatter", function (next) { + stringify( + [ + { + value: true, + }, + ], + { cast: { boolean: () => "X" } }, (err, data) => { - if (!err) { - data.should.eql("a,b,c\nfoo,NULL,bar\n"); - } + if (!err) data.should.eql("X\n"); next(err); }, ); }); - it("cast.undefined can emit unquoted NULL", function (next) { + it("cast.date formatter", function (next) { stringify( - [{ a: "foo", b: undefined, c: "bar" }], + [ + { + value: new Date(), + }, + ], + { cast: { date: () => "X" } }, + (err, data) => { + if (!err) data.should.eql("X\n"); + next(err); + }, + ); + }); + + it("cast.null can emit unquoted NULL", function (next) { + stringify( + [{ a: "foo", b: null, c: "bar" }], { header: true, cast: { - undefined: function () { + null: function () { return { value: "NULL", quoted: false }; }, }, @@ -96,4 +121,69 @@ describe("Option `cast` with null and undefined", function () { }, ); }); + + it("cast.number formatter", function (next) { + stringify( + [ + { + value: 3.14, + }, + ], + { cast: { number: (value) => "" + value * 2 } }, + (err, data) => { + if (!err) data.should.eql("6.28\n"); + next(err); + }, + ); + }); + + it("cast.object formatter", function (next) { + stringify( + [ + { + value: { a: 1 }, + }, + ], + { cast: { object: () => "X" } }, + (err, data) => { + if (!err) data.should.eql("X\n"); + next(err); + }, + ); + }); + + it("cast.string formatter", function (next) { + stringify( + [ + { + value: "ok", + }, + ], + { cast: { string: () => "X" } }, + (err, data) => { + if (!err) data.should.eql("X\n"); + next(err); + }, + ); + }); + + it("cast.undefined can emit unquoted NULL", function (next) { + stringify( + [{ a: "foo", b: undefined, c: "bar" }], + { + header: true, + cast: { + undefined: function () { + return { value: "NULL", quoted: false }; + }, + }, + }, + (err, data) => { + if (!err) { + data.should.eql("a,b,c\nfoo,NULL,bar\n"); + } + next(err); + }, + ); + }); });