Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions packages/csv-parse/lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export type ColumnOption<K = string> =
| false
| { name: K };

export interface OptionsNormalized<T = string[]> {
export interface OptionsNormalized<T = string[], U = T> {
auto_parse?: boolean | CastingFunction;
auto_parse_date?: boolean | CastingDateFunction;
/**
Expand Down Expand Up @@ -190,7 +190,8 @@ export interface OptionsNormalized<T = string[]> {
/**
* Alter and filter records by executing a user defined function.
*/
on_record?: (record: T, context: InfoRecord) => T | undefined;
on_record?: (record: U, context: InfoRecord) => T | null | undefined;
// on_record?: (record: T, context: InfoRecord) => T | undefined;
/**
* Optional character surrounding a field, one character only, defaults to double quotes.
*/
Expand Down Expand Up @@ -258,7 +259,7 @@ Note, could not `extends stream.TransformOptions` because encoding can be
BufferEncoding and undefined as well as null which is not defined in the
extended type.
*/
export interface Options<T = string[]> {
export interface Options<T = string[], U = T> {
/**
* If true, the parser will attempt to convert read data types to native types.
* @deprecated Use {@link cast}
Expand Down Expand Up @@ -366,8 +367,8 @@ export interface Options<T = string[]> {
/**
* Alter and filter records by executing a user defined function.
*/
on_record?: (record: T, context: InfoRecord) => T | null | undefined;
onRecord?: (record: T, context: InfoRecord) => T | null | undefined;
on_record?: (record: U, context: InfoRecord) => T | null | undefined | U;
onRecord?: (record: U, context: InfoRecord) => T | null | undefined | U;
/**
* Function called when an error occured if the `skip_records_with_error`
* option is activated.
Expand Down Expand Up @@ -479,13 +480,13 @@ export class CsvError extends Error {
);
}

type OptionsWithColumns<T> = Omit<Options<T>, "columns"> & {
export type OptionsWithColumns<T, U = T> = Omit<Options<T, U>, "columns"> & {
columns: Exclude<Options["columns"], undefined | false>;
};

declare function parse<T = unknown>(
declare function parse<T = unknown, U = T>(
input: string | Buffer | Uint8Array,
options: OptionsWithColumns<T>,
options: OptionsWithColumns<T, U>,
callback?: Callback<T>,
): Parser;
declare function parse(
Expand All @@ -494,8 +495,8 @@ declare function parse(
callback?: Callback,
): Parser;

declare function parse<T = unknown>(
options: OptionsWithColumns<T>,
declare function parse<T = unknown, U = T>(
options: OptionsWithColumns<T, U>,
callback?: Callback<T>,
): Parser;
declare function parse(options: Options, callback?: Callback): Parser;
Expand Down
1 change: 1 addition & 0 deletions packages/csv-parse/lib/stream.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export {
ColumnOption,
Options,
OptionsNormalized,
OptionsWithColumns,
Info,
InfoCallback,
InfoDataSet,
Expand Down
11 changes: 4 additions & 7 deletions packages/csv-parse/lib/sync.d.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import { Options } from "./index.js";
import { Options, OptionsWithColumns } from "./index.js";

type OptionsWithColumns<T> = Omit<Options<T>, "columns"> & {
columns: Exclude<Options["columns"], undefined | false>;
};

declare function parse<T = unknown>(
declare function parse<T = unknown, U = T>(
input: Buffer | string | Uint8Array,
options: OptionsWithColumns<T>,
options: OptionsWithColumns<T, U>,
): T[];
declare function parse(
input: Buffer | string | Uint8Array,
Expand All @@ -24,6 +20,7 @@ export {
ColumnOption,
Options,
OptionsNormalized,
OptionsWithColumns,
Info,
InfoCallback,
InfoDataSet,
Expand Down
18 changes: 15 additions & 3 deletions packages/csv-parse/test/api.arguments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,11 @@ describe("API arguments", function () {
});

it("options:object, callback:function; write data and get result in callback", function (next) {
const parser = parse({ columns: true }, (err, records) => {
interface RowType {
field_1: string;
field_2: string;
}
const parser = parse<RowType>({ columns: true }, (err, records) => {
if (err) return next(err);
records.should.eql([{ field_1: "value 1", field_2: "value 2" }]);
next();
Expand Down Expand Up @@ -123,7 +127,11 @@ describe("API arguments", function () {

describe("3 args", function () {
it("data:string, options:object, callback:function", function (next) {
parse(
interface RowType {
field_1: string;
field_2: string;
}
parse<RowType>(
"field_1,field_2\nvalue 1,value 2",
{ columns: true },
(err, records) => {
Expand All @@ -135,7 +143,11 @@ describe("API arguments", function () {
});

it("data:buffer, options:object, callback:function", function (next) {
parse(
interface RowType {
field_1: string;
field_2: string;
}
parse<RowType>(
Buffer.from("field_1,field_2\nvalue 1,value 2"),
{ columns: true },
(err, records) => {
Expand Down
160 changes: 103 additions & 57 deletions packages/csv-parse/test/api.sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,74 +3,120 @@ import dedent from "dedent";
import { parse } from "../lib/sync.js";

describe("API sync", function () {
it("take a string and return records", function () {
const records = parse("field_1,field_2\nvalue 1,value 2");
records.should.eql([
["field_1", "field_2"],
["value 1", "value 2"],
]);
});
describe("content", function () {
it("take a string and return records", function () {
const records = parse("field_1,field_2\nvalue 1,value 2");
records.should.eql([
["field_1", "field_2"],
["value 1", "value 2"],
]);
});

it("take a buffer and return records", function () {
const records = parse(Buffer.from("field_1,field_2\nvalue 1,value 2"));
records.should.eql([
["field_1", "field_2"],
["value 1", "value 2"],
]);
});
it("take a buffer and return records", function () {
const records = parse(Buffer.from("field_1,field_2\nvalue 1,value 2"));
records.should.eql([
["field_1", "field_2"],
["value 1", "value 2"],
]);
});

it("take a Uint8Array and return records", function () {
const records = parse(
new TextEncoder().encode("field_1,field_2\nvalue 1,value 2"),
);
records.should.eql([
["field_1", "field_2"],
["value 1", "value 2"],
]);
it("take a Uint8Array and return records", function () {
const records = parse(
new TextEncoder().encode("field_1,field_2\nvalue 1,value 2"),
);
records.should.eql([
["field_1", "field_2"],
["value 1", "value 2"],
]);
});
});

it("honors columns option", function () {
const records = parse("field_1,field_2\nvalue 1,value 2", {
columns: true,
describe("options", function () {
it("`columns` option without generic", function () {
// Parse returns unknown[]
const records = parse("field_1,field_2\nvalue 1,value 2", {
columns: true,
});
records.should.eql([{ field_1: "value 1", field_2: "value 2" }]);
});

it("`columns` option with generic", function () {
// Parse returns Record[]
interface Record {
field_1: string;
field_2: string;
}
const records: Record[] = parse<Record>(
"field_1,field_2\nvalue 1,value 2",
{
columns: true,
},
);
records.should.eql([{ field_1: "value 1", field_2: "value 2" }]);
});
records.should.eql([{ field_1: "value 1", field_2: "value 2" }]);
});

it("honors objname option", function () {
const records = parse("field_1,field_2\nname 1,value 1\nname 2,value 2", {
objname: "field_1",
columns: true,
it("`columns` and `on_record` options with generic", function () {
// Parse returns Record[]
interface RecordOriginal {
field_a: string;
field_b: string;
}
interface Record {
field_1: string;
field_2: string;
}
const records: Record[] = parse<Record, RecordOriginal>(
"field_a,field_b\nvalue 1,value 2",
{
columns: true,
on_record: (record: RecordOriginal) => ({
field_1: record.field_a,
field_2: record.field_b,
}),
},
);
records.should.eql([{ field_1: "value 1", field_2: "value 2" }]);
});
records.should.eql({
"name 1": { field_1: "name 1", field_2: "value 1" },
"name 2": { field_1: "name 2", field_2: "value 2" },

it("`objname` option", function () {
// Not good, parse returns unknown[]
const records = parse("field_1,field_2\nname 1,value 1\nname 2,value 2", {
objname: "field_1",
columns: true,
});
records.should.eql({
"name 1": { field_1: "name 1", field_2: "value 1" },
"name 2": { field_1: "name 2", field_2: "value 2" },
});
});
});

it("honors to_line", function () {
const records = parse("1\n2\n3\n4", { to_line: 2 });
records.should.eql([["1"], ["2"]]);
it("`to_line` option", function () {
const records = parse("1\n2\n3\n4", { to_line: 2 });
records.should.eql([["1"], ["2"]]);
});
});

it("catch errors", function () {
try {
parse("A,B\nB\nC,K", { trim: true });
throw Error("Error not catched");
} catch (err) {
if (!err) throw Error("Invalid assessment");
(err as Error).message.should.eql(
"Invalid Record Length: expect 2, got 1 on line 2",
);
}
});
describe("errors", function () {
it("catch errors", function () {
try {
parse("A,B\nB\nC,K", { trim: true });
throw Error("Error not catched");
} catch (err) {
if (!err) throw Error("Invalid assessment");
(err as Error).message.should.eql(
"Invalid Record Length: expect 2, got 1 on line 2",
);
}
});

it("catch err in last line while flushing", function () {
(() => {
parse(dedent`
headerA, headerB
A2, B2
A1, B1, C2, D2
`);
}).should.throw("Invalid Record Length: expect 2, got 4 on line 3");
it("catch err in last line while flushing", function () {
(() => {
parse(dedent`
headerA, headerB
A2, B2
A1, B1, C2, D2
`);
}).should.throw("Invalid Record Length: expect 2, got 4 on line 3");
});
});
});
22 changes: 21 additions & 1 deletion packages/csv-parse/test/api.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ describe("API Types", function () {
{
columns: true,
},
(error, records: unknown[] | undefined) => {
(error, records: unknown[]) => {
records;
next(error);
},
Expand All @@ -506,5 +506,25 @@ describe("API Types", function () {
},
);
});

it("Exposes U[] and T if columns and on_record are specified", function (next) {
type PersonOriginal = { surname: string; age: number };
parse<Person, PersonOriginal>(
"",
{
columns: true,
on_record: (record: PersonOriginal) => {
return {
name: record.surname,
age: record.age,
};
},
},
(error, records: Person[]) => {
records;
next(error);
},
);
});
});
});
29 changes: 29 additions & 0 deletions packages/csv-parse/test/option.on_record.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ describe("Option `on_record`", function () {
"a,b",
{
on_record: (record) => {
console.log(record[1]);
return [record[1], record[0]];
},
},
Expand Down Expand Up @@ -86,6 +87,34 @@ describe("Option `on_record`", function () {
},
);
});

it("with option `columns` (fix #461 #464 #466)", function (next) {
type RowTypeOriginal = { a: string; b: string };
type RowTypeFinal = { prop_a: string; prop_b: string };
parse<RowTypeFinal, RowTypeOriginal>(
"a,1\nb,2",
{
on_record: (record: RowTypeOriginal): RowTypeFinal => ({
prop_a: record.a,
prop_b: record.b,
}),
columns: ["a", "b"],
},
function (err, records: RowTypeFinal[]) {
records.should.eql([
{
prop_a: "a",
prop_b: "1",
},
{
prop_a: "b",
prop_b: "2",
},
]);
next();
},
);
});
});

describe("context", function () {
Expand Down