@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.
@@ -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.match(/,/g) || []).length;
452
- const secondCommaCount = (secondLine.match(/,/g) || []).length;
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: csvString.length,
815
+ inputLength: normalized.length,
772
816
  maxRows: clampedMaxRows,
773
817
  });
774
818
  // Detect and skip metadata line
775
- const lines = splitCsvLines(csvString);
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.match(/,/g) || []).length;
452
- const secondCommaCount = (secondLine.match(/,/g) || []).length;
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: csvString.length,
815
+ inputLength: normalized.length,
772
816
  maxRows: clampedMaxRows,
773
817
  });
774
818
  // Detect and skip metadata line
775
- const lines = splitCsvLines(csvString);
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.7",
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": {