@reventlessdev/rescript-fast-csv
v1.2.0-alpha.12
Published
ReScript bindings for fast-csv
Readme
@reventlessdev/rescript-fast-csv
⚠️ Alpha. APIs and on-disk formats can change without notice between releases. Pin exact versions and expect breaking changes.
ReScript bindings for fast-csv, a fast and flexible CSV parsing library for Node.js.
Install
pnpm add @reventlessdev/rescript-fast-csvAdd it to your rescript.json dependencies:
{
"dependencies": ["@reventlessdev/rescript-fast-csv"]
}Core API
Parsing Functions
parseFile
Parse a CSV file from the filesystem.
parseFile(~path: string, ~options: Options.t=?) => csvParserStreamExample:
FastCSV.parseFile(
~path="data.csv",
~options={
headers: Bool(true),
delimiter: ","
}
)parseStream
Parse a CSV from a Node.js readable stream.
parseStream(~stream: NodeStreams.Readable.t, ~options: Options.t=?) => csvParserStreamParser Options
Configure the CSV parser behavior using the Options.t type:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| objectMode | bool | true | Parse rows as objects with named fields |
| delimiter | string | "," | Character used to separate columns |
| quote | Js.Nullable.t<string> | '"' | Character used to quote fields |
| escape | string | '"' | Character used to escape quotes |
| headers | Bool(bool) | String(array<string>) | false | Use first row as headers or provide custom headers |
| renameHeaders | bool | false | Allow headers to be renamed |
| ignoreEmpty | bool | false | Skip empty rows |
| comment | string | null | Character that starts comment lines (skipped) |
| discardUnmappedfolumns | bool | false | Discard columns not in headers |
| strictColumnHandling | bool | false | Throw error if row column count differs from headers |
| trim | bool | false | Trim whitespace from both ends of fields |
| rtrim | bool | false | Trim whitespace from right end of fields |
| ltrim | bool | false | Trim whitespace from left end of fields |
Example:
{
Options.headers: Bool(true),
delimiter: ";",
trim: true,
ignoreEmpty: true,
comment: "#"
}Event Handling Pattern
The parser uses a chainable event handler pattern. Register handlers to process rows, handle errors, and track completion:
onData
Called for each valid row parsed from the CSV.
onData(csvParserStream, row => unit) => csvParserStreamExample:
parseFile(~path="users.csv")
->onData(row => {
Js.log2("Parsed row:", row)
})onError
Called when the parser encounters an error.
onError(csvParserStream, Js.Exn.t => unit) => csvParserStreamExample:
parseFile(~path="data.csv")
->onError(err => {
switch Js.Exn.message(err) {
| Some(msg) => Js.log2("Parse error:", msg)
| None => Js.log("Unknown parse error")
}
})onEnd
Called when parsing completes. Receives the total row count.
onEnd(csvParserStream, rowCount => unit) => csvParserStreamExample:
parseFile(~path="data.csv")
->onEnd(count => {
Js.log(`Finished parsing ${count->Int.toString} rows`)
})onInvalid
Called when a row fails validation. Only triggered if validation is registered.
onInvalid(csvParserStream, (row, rowNumber, option<reason>) => unit) => csvParserStreamExample:
parseFile(~path="data.csv")
->validate((row, callback) => {
// Validation logic
callback->toInvalid("Missing required field")
})
->onInvalid((row, rowNum, reason) => {
switch reason {
| Some(msg) => Js.log2(`Row ${rowNum->Int.toString} invalid:`, msg)
| None => Js.log2(`Row ${rowNum->Int.toString} has structural errors`, row)
}
})Complete Example: Processing User Data
// Parse a CSV file with user data, validate entries, transform them,
// and handle errors gracefully
type user = {
name: string,
email: string,
age: int,
}
let users = ref([])
FastCSV.parseFile(
~path="users.csv",
~options={
headers: Bool(true),
delimiter: ",",
trim: true,
ignoreEmpty: true,
}
)
->FastCSV.validate((row, callback) => {
// Check if required fields exist
switch (Dict.get(row, "name"), Dict.get(row, "email"), Dict.get(row, "age")) {
| (Some(_), Some(_), Some(_)) => callback->FastCSV_Helpers.toValid
| _ => callback->FastCSV_Helpers.toInvalid("Missing required fields (name, email, age)")
}
})
->FastCSV.transform((row, callback) => {
// Transform row data
let name = Dict.get(row, "name")->Option.getOr("")
let email = Dict.get(row, "email")->Option.getOr("")
let ageStr = Dict.get(row, "age")->Option.getOr("0")
// Add transformed fields back to row
Dict.set(row, "email", String.toLowerCase(email))
Dict.set(row, "name", String.trim(name))
callback->FastCSV_Helpers.toValidTransformation(row)
})
->FastCSV.onData(row => {
// Process each valid row
switch (
Dict.get(row, "name"),
Dict.get(row, "email"),
Dict.get(row, "age")->Option.flatMap(Int.fromString)
) {
| (Some(name), Some(email), Some(age)) => {
let user = {name, email, age}
users := Array.concat(users.contents, [user])
}
| _ => Js.log2("Skipping invalid row:", row)
}
})
->FastCSV.onInvalid((row, rowNumber, reason) => {
let msg = reason->Option.getOr("Structural error")
Js.log(`Row ${rowNumber->Int.toString}: ${msg}`)
})
->FastCSV.onError(err => {
switch Js.Exn.message(err) {
| Some(msg) => Js.log2("Parse error:", msg)
| None => Js.log("Unknown parsing error occurred")
}
})
->FastCSV.onEnd(rowCount => {
Js.log(`Successfully parsed ${rowCount->Int.toString} rows`)
Js.log2("Total valid users:", Array.length(users.contents))
})Validation
Simple Validation
Use validate to register a validation function:
parseFile(~path="data.csv")
->validate((row, callback) => {
let hasRequiredField = Dict.get(row, "id")->Option.isSome
if hasRequiredField {
callback->toValid
} else {
callback->toInvalid("Missing id field")
}
})Result-Based Validation
Use validateResult for cleaner validation with Result types:
parseFile(~path="data.csv")
->validateResult(row => {
switch Dict.get(row, "email") {
| Some(email) if String.includes(email, "@") => Ok()
| Some(_) => Error("Invalid email format")
| None => Error("Email field is missing")
}
})Multiple Validations
Use validateMultiple or validateMultipleResults to chain validations:
let validations = [
row => Dict.get(row, "name")->Option.isSome ? Ok() : Error("Missing name"),
row => Dict.get(row, "age")->Option.isSome ? Ok() : Error("Missing age"),
]
parseFile(~path="data.csv")
->validateMultipleResults(validations)Transformation
Transform row data during parsing:
parseFile(~path="data.csv")
->transform((row, callback) => {
// Normalize field names by removing empty keys
Dict.forEach(row, (value, key) => {
if String.length(key) == 0 {
Dict.delete(row, key)
}
})
// Convert certain fields to uppercase
switch Dict.get(row, "code") {
| Some(code) => Dict.set(row, "code", String.toUpperCase(code))
| None => ()
}
callback->toValidTransformation(row)
})Helper Functions
toValid(callback)- Mark validation as successfultoInvalid(callback, reason)- Mark validation as failed with reasontoError(callback, reason)- Mark validation as errortoValidTransformation(callback, row)- Complete transformation successfullytoErrorTransformation(callback, reason)- Mark transformation as failed
Links
- 📚 Documentation — docs.reventless.dev
- 📦 Repository — ReventlessDev/reventless-core
- 📋 Changelog
