@juspay/neurolink
Version:
The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002
85 lines (84 loc) • 3.25 kB
JavaScript
/**
* Shared, RFC-4180-aware delimiter-separated-value helpers.
*
* Consolidates quote-aware field counting/splitting that was previously
* duplicated across csvProcessor.ts and fileDetector.ts (#359), and adds
* delimiter detection so TSV / semicolon / pipe files parse correctly (#361).
* Keeping these in one module prevents the same quoting edge case from having
* to be fixed in three places and drifting out of sync.
*/
/** Delimiter candidates, in tie-break preference order (comma first). */
export const CANDIDATE_DELIMITERS = [",", "\t", ";", "|"];
/**
* Split a single line into fields, respecting RFC-4180 quoting: a delimiter
* inside a `"…"` quoted field does not split, and `""` is an escaped quote.
*/
export function splitCSVFields(line, delimiter = ",") {
const fields = [];
let field = "";
let inQuotes = false;
for (let i = 0; i < line.length; i++) {
const ch = line[i];
if (ch === '"') {
if (inQuotes && line[i + 1] === '"') {
field += '"';
i++; // consume the escaped quote
continue;
}
inQuotes = !inQuotes;
}
else if (ch === delimiter && !inQuotes) {
fields.push(field);
field = "";
}
else {
field += ch;
}
}
fields.push(field);
return fields;
}
/** Count fields in a single line, respecting RFC-4180 quoting. */
export function countCSVColumns(line, delimiter = ",") {
return splitCSVFields(line, delimiter).length;
}
/**
* Detect the delimiter of DSV content by testing comma/tab/semicolon/pipe
* against up to 5 sampled non-empty lines and keeping the candidate with the
* most consistent (>1) column count. Comma wins ties and the "nothing splits"
* case, so plain CSVs are never reclassified. An optional extension hint
* (`tsv` → tab, `psv` → pipe) breaks ambiguity in the file's favor.
*/
export function detectDelimiter(lines, extensionHint) {
const sample = lines.filter((l) => l.trim() !== "").slice(0, 5);
if (sample.length === 0) {
return ",";
}
const hint = extensionHint?.toLowerCase().replace(/^\./, "");
const hinted = hint === "tsv" ? "\t" : hint === "psv" ? "|" : undefined;
let best = ",";
let bestScore = -1;
for (const delim of CANDIDATE_DELIMITERS) {
const counts = sample.map((l) => countCSVColumns(l, delim));
const headerCols = counts[0];
if (headerCols <= 1) {
continue; // this delimiter doesn't split the header — not the delimiter
}
const consistent = counts.every((c) => c === headerCols);
// Prefer: a matching extension hint, then consistency, then more columns,
// with a small comma bias so comma stays the default on ties.
const score = (delim === hinted ? 1000 : 0) +
(consistent ? 100 : 0) +
headerCols +
(delim === "," ? 0.5 : 0);
if (score > bestScore) {
bestScore = score;
best = delim;
}
}
// Nothing split multi-column, but the extension says otherwise — honor it.
if (bestScore < 0 && hinted) {
return hinted;
}
return best;
}