@dynamic-entity/server 2.1.1 → 2.3.0

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/express.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { deriveImportColumns, buildTemplateSpec, CORE_VERSION, suggestMapping, toCsv, escapeFormula, validateMappingPlan, applyMapping, createCsvReader, padRow } from '@dynamic-entity/core';
1
+ import { deriveImportColumns, buildTemplateSpec, CORE_VERSION, cellText, arrayBoundFor, suggestMapping, decimalMarkFor, toCsv, escapeFormula, validateMappingPlan, applyMapping, completeFirstLine, padRow, detectDelimiter, createCsvReader } from '@dynamic-entity/core';
2
2
  import express from 'express';
3
3
  import Busboy from 'busboy';
4
4
  import ExcelJS from 'exceljs';
@@ -235,17 +235,6 @@ function readPlanField(fields) {
235
235
  throw new ImportError("INVALID_PLAN", "The mapping plan is not valid JSON.", { cause });
236
236
  }
237
237
  }
238
-
239
- // src/cell-text.ts
240
- function sampleText(raw) {
241
- if (raw === null || raw === void 0) return "";
242
- if (raw instanceof Date) {
243
- if (Number.isNaN(raw.getTime())) return "";
244
- const iso = raw.toISOString();
245
- return raw.getTime() % 864e5 === 0 ? iso.slice(0, 10) : iso;
246
- }
247
- return typeof raw === "string" ? raw : String(raw);
248
- }
249
238
  var LOCAL_HEADER = 67324752;
250
239
  var CENTRAL_HEADER = 33639248;
251
240
  var END_OF_CENTRAL = 101010256;
@@ -562,6 +551,12 @@ async function guardZip(source, limits) {
562
551
  }
563
552
  }
564
553
  if (!sheet) {
554
+ if (!kept.has("xl/workbook.xml")) {
555
+ throw new ImportError(
556
+ "UNSUPPORTED_FORMAT",
557
+ "This is a zip archive that is not an Excel workbook. Upload an .xlsx or .csv file."
558
+ );
559
+ }
565
560
  throw new ImportError("MALFORMED_FILE", "This workbook has no worksheet.");
566
561
  }
567
562
  const ordered = [...WANTED, sheet.name].map((wanted) => kept.get(wanted)).filter((entry) => entry !== void 0);
@@ -656,13 +651,36 @@ function detectFormat(head) {
656
651
  }
657
652
  return "csv";
658
653
  }
659
- async function* csvRows(stream) {
654
+ async function* csvRows(stream, limits, found) {
660
655
  const decoder = new TextDecoder("utf-8");
661
- const reader = createCsvReader();
656
+ const maxHeader = limits.maxCellLength * limits.maxColumns + limits.maxColumns;
657
+ let held = "";
658
+ let reader = null;
659
+ const open = (headerLine) => {
660
+ found.delimiter = detectDelimiter(headerLine);
661
+ return createCsvReader({ delimiter: found.delimiter });
662
+ };
662
663
  for await (const chunk of stream) {
663
- for (const row of reader.push(decoder.decode(chunk, { stream: true }))) yield row;
664
+ const text = decoder.decode(chunk, { stream: true });
665
+ if (!reader) {
666
+ held += text;
667
+ const line = completeFirstLine(held);
668
+ if (line === null) {
669
+ if (held.length > maxHeader) {
670
+ throw new ImportError("SHEET_TOO_LARGE", "The header row is longer than any row may be.");
671
+ }
672
+ continue;
673
+ }
674
+ reader = open(line);
675
+ for (const row of reader.push(held)) yield row;
676
+ held = "";
677
+ continue;
678
+ }
679
+ for (const row of reader.push(text)) yield row;
664
680
  }
665
- for (const row of reader.push(decoder.decode())) yield row;
681
+ const rest = held + decoder.decode();
682
+ reader ??= open(completeFirstLine(rest) ?? rest);
683
+ for (const row of reader.push(rest)) yield row;
666
684
  for (const row of reader.end()) yield row;
667
685
  }
668
686
  async function* guardRows(rows, width, limits) {
@@ -697,10 +715,11 @@ async function readSheet(options) {
697
715
  const counted = limitBytes(toByteStream(options.stream), limits.maxBytes);
698
716
  const { head, stream } = await peek(counted, MAGIC_BYTES);
699
717
  const format = detectFormat(head);
700
- const rows = format === "xlsx" ? xlsxRows(stream, limits) : csvRows(stream);
718
+ const found = {};
719
+ const rows = format === "xlsx" ? xlsxRows(stream, limits) : csvRows(stream, limits, found);
701
720
  const iterator = rows[Symbol.asyncIterator]();
702
721
  const first = await iterator.next();
703
- const headers = first.done ? [] : first.value.map(sampleText);
722
+ const headers = first.done ? [] : first.value.map(cellText);
704
723
  if (headers.length > limits.maxColumns) {
705
724
  throw new ImportError(
706
725
  "SHEET_TOO_LARGE",
@@ -711,7 +730,8 @@ async function readSheet(options) {
711
730
  return {
712
731
  format,
713
732
  headers,
714
- rows: closing(guardRows(remaining, headers.length, limits), options.stream)
733
+ rows: closing(guardRows(remaining, headers.length, limits), options.stream),
734
+ ...found.delimiter ? { delimiter: found.delimiter } : {}
715
735
  };
716
736
  } catch (error) {
717
737
  destroySource(options.stream);
@@ -765,6 +785,7 @@ async function runImport(options) {
765
785
  filename: options.filename,
766
786
  limits
767
787
  });
788
+ const decimal = options.decimal ?? decimalMarkFor(sheet.delimiter);
768
789
  const errors = [];
769
790
  let errorCount = 0;
770
791
  let imported = 0;
@@ -780,6 +801,7 @@ async function runImport(options) {
780
801
  batch = [];
781
802
  const result = applyMapping(rows, plan, config, {
782
803
  lang: options.lang,
804
+ decimal,
783
805
  rules: options.rules,
784
806
  lookups: options.lookups,
785
807
  firstRowNumber: firstRow
@@ -842,19 +864,22 @@ async function previewSheet(options) {
842
864
  try {
843
865
  for await (const row of sheet.rows) {
844
866
  rowCount++;
845
- if (sample.length < limits.sampleRows) sample.push(row.map(sampleText));
867
+ if (sample.length < limits.sampleRows) sample.push(row.map(cellText));
846
868
  }
847
869
  } catch (error) {
848
870
  destroySource(options.stream);
849
871
  throw error;
850
872
  }
851
- const { columns } = deriveImportColumns(options.config, { lang: options.lang });
873
+ const arrayBound = arrayBoundFor(sheet.headers, options.config, null, options.lang);
874
+ const { columns } = deriveImportColumns(options.config, { lang: options.lang, maxArrayRows: arrayBound });
852
875
  return {
853
876
  headers: sheet.headers,
854
877
  sample,
855
878
  rowCount,
856
879
  suggestion: suggestMapping(sheet.headers, columns, options.config?.entity ?? ""),
857
- format: sheet.format
880
+ format: sheet.format,
881
+ ...sheet.delimiter ? { delimiter: sheet.delimiter } : {},
882
+ arrayBound
858
883
  };
859
884
  }
860
885
  var TEMPLATE_EXTENSION = { csv: "csv", xlsx: "xlsx" };
@@ -1031,6 +1056,8 @@ function createImportRouter(options) {
1031
1056
  sample: preview.sample,
1032
1057
  suggestion: preview.suggestion,
1033
1058
  rowCount: preview.rowCount,
1059
+ arrayBound: preview.arrayBound,
1060
+ ...preview.delimiter ? { delimiter: preview.delimiter } : {},
1034
1061
  engineVersion: CORE_VERSION
1035
1062
  };
1036
1063
  response.json(body);