@juspay/neurolink 9.88.7 → 9.88.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/browser/neurolink.min.js +287 -287
- package/dist/lib/utils/csvProcessor.js +57 -6
- package/dist/utils/csvProcessor.js +57 -6
- package/package.json +1 -1
|
@@ -439,6 +439,33 @@ function detectHasHeaders(headerValues, dataRows) {
|
|
|
439
439
|
* - Lines with significantly different delimiter count than line 2
|
|
440
440
|
* - Lines that don't match CSV structure of subsequent lines
|
|
441
441
|
*/
|
|
442
|
+
/** Strip a leading UTF-8 BOM (U+FEFF) if present. */
|
|
443
|
+
function stripBom(text) {
|
|
444
|
+
return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Count commas that fall OUTSIDE double-quoted fields (RFC 4180, escaped-quote
|
|
448
|
+
* aware). A quoted comma is field content, not a column separator, so counting
|
|
449
|
+
* naively would misclassify a quoted-comma header line.
|
|
450
|
+
*/
|
|
451
|
+
function countUnquotedCommas(line) {
|
|
452
|
+
let count = 0;
|
|
453
|
+
let inQuotes = false;
|
|
454
|
+
for (let i = 0; i < line.length; i++) {
|
|
455
|
+
const ch = line[i];
|
|
456
|
+
if (ch === '"') {
|
|
457
|
+
if (inQuotes && line[i + 1] === '"') {
|
|
458
|
+
i++;
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
inQuotes = !inQuotes;
|
|
462
|
+
}
|
|
463
|
+
else if (ch === "," && !inQuotes) {
|
|
464
|
+
count++;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return count;
|
|
468
|
+
}
|
|
442
469
|
function isMetadataLine(lines) {
|
|
443
470
|
if (!lines[0] || lines.length < 2) {
|
|
444
471
|
return false;
|
|
@@ -448,8 +475,8 @@ function isMetadataLine(lines) {
|
|
|
448
475
|
if (firstLine.match(/^sep=/i)) {
|
|
449
476
|
return true;
|
|
450
477
|
}
|
|
451
|
-
const firstCommaCount = (firstLine
|
|
452
|
-
const secondCommaCount = (secondLine
|
|
478
|
+
const firstCommaCount = countUnquotedCommas(firstLine);
|
|
479
|
+
const secondCommaCount = countUnquotedCommas(secondLine);
|
|
453
480
|
if (firstCommaCount === 0 && secondCommaCount > 0) {
|
|
454
481
|
return true;
|
|
455
482
|
}
|
|
@@ -690,6 +717,9 @@ export class CSVProcessor {
|
|
|
690
717
|
* @returns Array of row objects
|
|
691
718
|
*/
|
|
692
719
|
static async parseCSVFile(filePath, maxRows = 1000) {
|
|
720
|
+
if (typeof filePath !== "string" || filePath.trim().length === 0) {
|
|
721
|
+
throw new Error("CSVProcessor.parseCSVFile: filePath must be a non-empty string");
|
|
722
|
+
}
|
|
693
723
|
const clampedMaxRows = Math.max(1, Math.min(10000, maxRows));
|
|
694
724
|
const fs = await import("fs");
|
|
695
725
|
logger.debug("[CSVProcessor] Starting file parsing", {
|
|
@@ -715,6 +745,8 @@ export class CSVProcessor {
|
|
|
715
745
|
}
|
|
716
746
|
});
|
|
717
747
|
lineReader.on("end", () => resolve());
|
|
748
|
+
// Metadata sniffing is best-effort — a read error here must not crash.
|
|
749
|
+
lineReader.on("error", () => resolve());
|
|
718
750
|
});
|
|
719
751
|
await fileHandle.close();
|
|
720
752
|
const hasMetadataLine = isMetadataLine(firstLines);
|
|
@@ -732,6 +764,12 @@ export class CSVProcessor {
|
|
|
732
764
|
source.destroy();
|
|
733
765
|
parser.destroy();
|
|
734
766
|
};
|
|
767
|
+
// .pipe() does not forward source errors (disk read/permission failures)
|
|
768
|
+
// to the parser, so listen on the source directly or it throws unhandled.
|
|
769
|
+
source.on("error", (error) => {
|
|
770
|
+
abort();
|
|
771
|
+
reject(new Error(`[CSVProcessor] CSV file read failed after ${count} row(s) (${filePath}): ${error.message}`));
|
|
772
|
+
});
|
|
735
773
|
source
|
|
736
774
|
.pipe(parser)
|
|
737
775
|
.on("data", (row) => {
|
|
@@ -753,7 +791,7 @@ export class CSVProcessor {
|
|
|
753
791
|
})
|
|
754
792
|
.on("error", (error) => {
|
|
755
793
|
logger.error("[CSVProcessor] File parsing failed:", error);
|
|
756
|
-
reject(error);
|
|
794
|
+
reject(new Error(`[CSVProcessor] CSV parsing failed after ${count} row(s): ${error.message}`));
|
|
757
795
|
});
|
|
758
796
|
});
|
|
759
797
|
}
|
|
@@ -766,13 +804,19 @@ export class CSVProcessor {
|
|
|
766
804
|
* @returns Array of row objects
|
|
767
805
|
*/
|
|
768
806
|
static async parseCSVString(csvString, maxRows = 1000) {
|
|
807
|
+
if (typeof csvString !== "string" || csvString.trim().length === 0) {
|
|
808
|
+
throw new Error("CSVProcessor.parseCSVString: csvString must be a non-empty string");
|
|
809
|
+
}
|
|
810
|
+
// Strip a leading UTF-8 BOM (common in Excel exports) so it does not glue
|
|
811
|
+
// onto the first column name and break downstream key lookups.
|
|
812
|
+
const normalized = stripBom(csvString);
|
|
769
813
|
const clampedMaxRows = Math.max(1, Math.min(10000, maxRows));
|
|
770
814
|
logger.debug("[CSVProcessor] Starting string parsing", {
|
|
771
|
-
inputLength:
|
|
815
|
+
inputLength: normalized.length,
|
|
772
816
|
maxRows: clampedMaxRows,
|
|
773
817
|
});
|
|
774
818
|
// Detect and skip metadata line
|
|
775
|
-
const lines = splitCsvLines(
|
|
819
|
+
const lines = splitCsvLines(normalized);
|
|
776
820
|
const hasMetadataLine = isMetadataLine(lines);
|
|
777
821
|
const csvData = hasMetadataLine
|
|
778
822
|
? lines.slice(1).join("\n")
|
|
@@ -789,6 +833,13 @@ export class CSVProcessor {
|
|
|
789
833
|
source.destroy();
|
|
790
834
|
parser.destroy();
|
|
791
835
|
};
|
|
836
|
+
// .pipe() does not forward source-stream errors to the destination, so a
|
|
837
|
+
// source failure must be listened for directly or it throws as an
|
|
838
|
+
// unhandled EventEmitter error and can crash the process.
|
|
839
|
+
source.on("error", (error) => {
|
|
840
|
+
abort();
|
|
841
|
+
reject(new Error(`[CSVProcessor] CSV source stream failed after ${count} row(s): ${error.message}`));
|
|
842
|
+
});
|
|
792
843
|
source
|
|
793
844
|
.pipe(parser)
|
|
794
845
|
.on("data", (row) => {
|
|
@@ -806,7 +857,7 @@ export class CSVProcessor {
|
|
|
806
857
|
})
|
|
807
858
|
.on("error", (error) => {
|
|
808
859
|
logger.error("[CSVProcessor] Parsing failed:", error);
|
|
809
|
-
reject(error);
|
|
860
|
+
reject(new Error(`[CSVProcessor] CSV parsing failed after ${count} row(s): ${error.message}`));
|
|
810
861
|
});
|
|
811
862
|
});
|
|
812
863
|
}
|
|
@@ -439,6 +439,33 @@ function detectHasHeaders(headerValues, dataRows) {
|
|
|
439
439
|
* - Lines with significantly different delimiter count than line 2
|
|
440
440
|
* - Lines that don't match CSV structure of subsequent lines
|
|
441
441
|
*/
|
|
442
|
+
/** Strip a leading UTF-8 BOM (U+FEFF) if present. */
|
|
443
|
+
function stripBom(text) {
|
|
444
|
+
return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Count commas that fall OUTSIDE double-quoted fields (RFC 4180, escaped-quote
|
|
448
|
+
* aware). A quoted comma is field content, not a column separator, so counting
|
|
449
|
+
* naively would misclassify a quoted-comma header line.
|
|
450
|
+
*/
|
|
451
|
+
function countUnquotedCommas(line) {
|
|
452
|
+
let count = 0;
|
|
453
|
+
let inQuotes = false;
|
|
454
|
+
for (let i = 0; i < line.length; i++) {
|
|
455
|
+
const ch = line[i];
|
|
456
|
+
if (ch === '"') {
|
|
457
|
+
if (inQuotes && line[i + 1] === '"') {
|
|
458
|
+
i++;
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
inQuotes = !inQuotes;
|
|
462
|
+
}
|
|
463
|
+
else if (ch === "," && !inQuotes) {
|
|
464
|
+
count++;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return count;
|
|
468
|
+
}
|
|
442
469
|
function isMetadataLine(lines) {
|
|
443
470
|
if (!lines[0] || lines.length < 2) {
|
|
444
471
|
return false;
|
|
@@ -448,8 +475,8 @@ function isMetadataLine(lines) {
|
|
|
448
475
|
if (firstLine.match(/^sep=/i)) {
|
|
449
476
|
return true;
|
|
450
477
|
}
|
|
451
|
-
const firstCommaCount = (firstLine
|
|
452
|
-
const secondCommaCount = (secondLine
|
|
478
|
+
const firstCommaCount = countUnquotedCommas(firstLine);
|
|
479
|
+
const secondCommaCount = countUnquotedCommas(secondLine);
|
|
453
480
|
if (firstCommaCount === 0 && secondCommaCount > 0) {
|
|
454
481
|
return true;
|
|
455
482
|
}
|
|
@@ -690,6 +717,9 @@ export class CSVProcessor {
|
|
|
690
717
|
* @returns Array of row objects
|
|
691
718
|
*/
|
|
692
719
|
static async parseCSVFile(filePath, maxRows = 1000) {
|
|
720
|
+
if (typeof filePath !== "string" || filePath.trim().length === 0) {
|
|
721
|
+
throw new Error("CSVProcessor.parseCSVFile: filePath must be a non-empty string");
|
|
722
|
+
}
|
|
693
723
|
const clampedMaxRows = Math.max(1, Math.min(10000, maxRows));
|
|
694
724
|
const fs = await import("fs");
|
|
695
725
|
logger.debug("[CSVProcessor] Starting file parsing", {
|
|
@@ -715,6 +745,8 @@ export class CSVProcessor {
|
|
|
715
745
|
}
|
|
716
746
|
});
|
|
717
747
|
lineReader.on("end", () => resolve());
|
|
748
|
+
// Metadata sniffing is best-effort — a read error here must not crash.
|
|
749
|
+
lineReader.on("error", () => resolve());
|
|
718
750
|
});
|
|
719
751
|
await fileHandle.close();
|
|
720
752
|
const hasMetadataLine = isMetadataLine(firstLines);
|
|
@@ -732,6 +764,12 @@ export class CSVProcessor {
|
|
|
732
764
|
source.destroy();
|
|
733
765
|
parser.destroy();
|
|
734
766
|
};
|
|
767
|
+
// .pipe() does not forward source errors (disk read/permission failures)
|
|
768
|
+
// to the parser, so listen on the source directly or it throws unhandled.
|
|
769
|
+
source.on("error", (error) => {
|
|
770
|
+
abort();
|
|
771
|
+
reject(new Error(`[CSVProcessor] CSV file read failed after ${count} row(s) (${filePath}): ${error.message}`));
|
|
772
|
+
});
|
|
735
773
|
source
|
|
736
774
|
.pipe(parser)
|
|
737
775
|
.on("data", (row) => {
|
|
@@ -753,7 +791,7 @@ export class CSVProcessor {
|
|
|
753
791
|
})
|
|
754
792
|
.on("error", (error) => {
|
|
755
793
|
logger.error("[CSVProcessor] File parsing failed:", error);
|
|
756
|
-
reject(error);
|
|
794
|
+
reject(new Error(`[CSVProcessor] CSV parsing failed after ${count} row(s): ${error.message}`));
|
|
757
795
|
});
|
|
758
796
|
});
|
|
759
797
|
}
|
|
@@ -766,13 +804,19 @@ export class CSVProcessor {
|
|
|
766
804
|
* @returns Array of row objects
|
|
767
805
|
*/
|
|
768
806
|
static async parseCSVString(csvString, maxRows = 1000) {
|
|
807
|
+
if (typeof csvString !== "string" || csvString.trim().length === 0) {
|
|
808
|
+
throw new Error("CSVProcessor.parseCSVString: csvString must be a non-empty string");
|
|
809
|
+
}
|
|
810
|
+
// Strip a leading UTF-8 BOM (common in Excel exports) so it does not glue
|
|
811
|
+
// onto the first column name and break downstream key lookups.
|
|
812
|
+
const normalized = stripBom(csvString);
|
|
769
813
|
const clampedMaxRows = Math.max(1, Math.min(10000, maxRows));
|
|
770
814
|
logger.debug("[CSVProcessor] Starting string parsing", {
|
|
771
|
-
inputLength:
|
|
815
|
+
inputLength: normalized.length,
|
|
772
816
|
maxRows: clampedMaxRows,
|
|
773
817
|
});
|
|
774
818
|
// Detect and skip metadata line
|
|
775
|
-
const lines = splitCsvLines(
|
|
819
|
+
const lines = splitCsvLines(normalized);
|
|
776
820
|
const hasMetadataLine = isMetadataLine(lines);
|
|
777
821
|
const csvData = hasMetadataLine
|
|
778
822
|
? lines.slice(1).join("\n")
|
|
@@ -789,6 +833,13 @@ export class CSVProcessor {
|
|
|
789
833
|
source.destroy();
|
|
790
834
|
parser.destroy();
|
|
791
835
|
};
|
|
836
|
+
// .pipe() does not forward source-stream errors to the destination, so a
|
|
837
|
+
// source failure must be listened for directly or it throws as an
|
|
838
|
+
// unhandled EventEmitter error and can crash the process.
|
|
839
|
+
source.on("error", (error) => {
|
|
840
|
+
abort();
|
|
841
|
+
reject(new Error(`[CSVProcessor] CSV source stream failed after ${count} row(s): ${error.message}`));
|
|
842
|
+
});
|
|
792
843
|
source
|
|
793
844
|
.pipe(parser)
|
|
794
845
|
.on("data", (row) => {
|
|
@@ -806,7 +857,7 @@ export class CSVProcessor {
|
|
|
806
857
|
})
|
|
807
858
|
.on("error", (error) => {
|
|
808
859
|
logger.error("[CSVProcessor] Parsing failed:", error);
|
|
809
|
-
reject(error);
|
|
860
|
+
reject(new Error(`[CSVProcessor] CSV parsing failed after ${count} row(s): ${error.message}`));
|
|
810
861
|
});
|
|
811
862
|
});
|
|
812
863
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "9.88.
|
|
3
|
+
"version": "9.88.8",
|
|
4
4
|
"packageManager": "pnpm@10.15.1",
|
|
5
5
|
"description": "TypeScript AI SDK with 24+ LLM providers behind one consistent API. MCP-native (58+ servers), voice TTS/STT/realtime, RAG, agents, memory, context compaction. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · DeepSeek · NVIDIA NIM and more.",
|
|
6
6
|
"author": {
|