@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/index.mjs CHANGED
@@ -1,7 +1,8 @@
1
+ import { padRow, cellText, decimalMarkFor, arrayBoundFor, deriveImportColumns, suggestMapping, toCsv, escapeFormula, applyMapping, completeFirstLine, validateMappingPlan, detectDelimiter, createCsvReader } from '@dynamic-entity/core';
2
+ export { cellText as sampleText } from '@dynamic-entity/core';
1
3
  import zlib from 'zlib';
2
4
  import ExcelJS from 'exceljs';
3
5
  import { Readable } from 'stream';
4
- import { padRow, deriveImportColumns, suggestMapping, toCsv, escapeFormula, applyMapping, createCsvReader, validateMappingPlan } from '@dynamic-entity/core';
5
6
 
6
7
  // src/limits.ts
7
8
  var DEFAULT_LIMITS = {
@@ -133,17 +134,6 @@ function destroySource(source) {
133
134
  } catch {
134
135
  }
135
136
  }
136
-
137
- // src/cell-text.ts
138
- function sampleText(raw) {
139
- if (raw === null || raw === void 0) return "";
140
- if (raw instanceof Date) {
141
- if (Number.isNaN(raw.getTime())) return "";
142
- const iso = raw.toISOString();
143
- return raw.getTime() % 864e5 === 0 ? iso.slice(0, 10) : iso;
144
- }
145
- return typeof raw === "string" ? raw : String(raw);
146
- }
147
137
  var LOCAL_HEADER = 67324752;
148
138
  var CENTRAL_HEADER = 33639248;
149
139
  var END_OF_CENTRAL = 101010256;
@@ -460,6 +450,12 @@ async function guardZip(source, limits) {
460
450
  }
461
451
  }
462
452
  if (!sheet) {
453
+ if (!kept.has("xl/workbook.xml")) {
454
+ throw new ImportError(
455
+ "UNSUPPORTED_FORMAT",
456
+ "This is a zip archive that is not an Excel workbook. Upload an .xlsx or .csv file."
457
+ );
458
+ }
463
459
  throw new ImportError("MALFORMED_FILE", "This workbook has no worksheet.");
464
460
  }
465
461
  const ordered = [...WANTED, sheet.name].map((wanted) => kept.get(wanted)).filter((entry) => entry !== void 0);
@@ -550,13 +546,36 @@ function detectFormat(head) {
550
546
  }
551
547
  return "csv";
552
548
  }
553
- async function* csvRows(stream) {
549
+ async function* csvRows(stream, limits, found) {
554
550
  const decoder = new TextDecoder("utf-8");
555
- const reader = createCsvReader();
551
+ const maxHeader = limits.maxCellLength * limits.maxColumns + limits.maxColumns;
552
+ let held = "";
553
+ let reader = null;
554
+ const open = (headerLine) => {
555
+ found.delimiter = detectDelimiter(headerLine);
556
+ return createCsvReader({ delimiter: found.delimiter });
557
+ };
556
558
  for await (const chunk of stream) {
557
- for (const row of reader.push(decoder.decode(chunk, { stream: true }))) yield row;
559
+ const text = decoder.decode(chunk, { stream: true });
560
+ if (!reader) {
561
+ held += text;
562
+ const line = completeFirstLine(held);
563
+ if (line === null) {
564
+ if (held.length > maxHeader) {
565
+ throw new ImportError("SHEET_TOO_LARGE", "The header row is longer than any row may be.");
566
+ }
567
+ continue;
568
+ }
569
+ reader = open(line);
570
+ for (const row of reader.push(held)) yield row;
571
+ held = "";
572
+ continue;
573
+ }
574
+ for (const row of reader.push(text)) yield row;
558
575
  }
559
- for (const row of reader.push(decoder.decode())) yield row;
576
+ const rest = held + decoder.decode();
577
+ reader ??= open(completeFirstLine(rest) ?? rest);
578
+ for (const row of reader.push(rest)) yield row;
560
579
  for (const row of reader.end()) yield row;
561
580
  }
562
581
  async function* guardRows(rows, width, limits) {
@@ -591,10 +610,11 @@ async function readSheet(options) {
591
610
  const counted = limitBytes(toByteStream(options.stream), limits.maxBytes);
592
611
  const { head, stream } = await peek(counted, MAGIC_BYTES);
593
612
  const format = detectFormat(head);
594
- const rows = format === "xlsx" ? xlsxRows(stream, limits) : csvRows(stream);
613
+ const found = {};
614
+ const rows = format === "xlsx" ? xlsxRows(stream, limits) : csvRows(stream, limits, found);
595
615
  const iterator = rows[Symbol.asyncIterator]();
596
616
  const first = await iterator.next();
597
- const headers = first.done ? [] : first.value.map(sampleText);
617
+ const headers = first.done ? [] : first.value.map(cellText);
598
618
  if (headers.length > limits.maxColumns) {
599
619
  throw new ImportError(
600
620
  "SHEET_TOO_LARGE",
@@ -605,7 +625,8 @@ async function readSheet(options) {
605
625
  return {
606
626
  format,
607
627
  headers,
608
- rows: closing(guardRows(remaining, headers.length, limits), options.stream)
628
+ rows: closing(guardRows(remaining, headers.length, limits), options.stream),
629
+ ...found.delimiter ? { delimiter: found.delimiter } : {}
609
630
  };
610
631
  } catch (error) {
611
632
  destroySource(options.stream);
@@ -657,6 +678,7 @@ async function runImport(options) {
657
678
  filename: options.filename,
658
679
  limits
659
680
  });
681
+ const decimal = options.decimal ?? decimalMarkFor(sheet.delimiter);
660
682
  const errors = [];
661
683
  let errorCount = 0;
662
684
  let imported = 0;
@@ -672,6 +694,7 @@ async function runImport(options) {
672
694
  batch = [];
673
695
  const result = applyMapping(rows, plan, config, {
674
696
  lang: options.lang,
697
+ decimal,
675
698
  rules: options.rules,
676
699
  lookups: options.lookups,
677
700
  firstRowNumber: firstRow
@@ -734,19 +757,22 @@ async function previewSheet(options) {
734
757
  try {
735
758
  for await (const row of sheet.rows) {
736
759
  rowCount++;
737
- if (sample.length < limits.sampleRows) sample.push(row.map(sampleText));
760
+ if (sample.length < limits.sampleRows) sample.push(row.map(cellText));
738
761
  }
739
762
  } catch (error) {
740
763
  destroySource(options.stream);
741
764
  throw error;
742
765
  }
743
- const { columns } = deriveImportColumns(options.config, { lang: options.lang });
766
+ const arrayBound = arrayBoundFor(sheet.headers, options.config, null, options.lang);
767
+ const { columns } = deriveImportColumns(options.config, { lang: options.lang, maxArrayRows: arrayBound });
744
768
  return {
745
769
  headers: sheet.headers,
746
770
  sample,
747
771
  rowCount,
748
772
  suggestion: suggestMapping(sheet.headers, columns, options.config?.entity ?? ""),
749
- format: sheet.format
773
+ format: sheet.format,
774
+ ...sheet.delimiter ? { delimiter: sheet.delimiter } : {},
775
+ arrayBound
750
776
  };
751
777
  }
752
778
  var TEMPLATE_EXTENSION = { csv: "csv", xlsx: "xlsx" };
@@ -795,6 +821,6 @@ function sheetName(name) {
795
821
  return (cleaned || "Import").slice(0, 31);
796
822
  }
797
823
 
798
- export { DEFAULT_LIMITS, ImportError, TEMPLATE_EXTENSION, TEMPLATE_MEDIA_TYPE, cellValue, destroySource, detectFormat, guardRows, guardZip, limitBytes, peek, previewSheet, readSheet, resolveLimits, runImport, sampleText, toByteStream, toErrorBody, writeTemplate, xlsxRows };
824
+ export { DEFAULT_LIMITS, ImportError, TEMPLATE_EXTENSION, TEMPLATE_MEDIA_TYPE, cellValue, destroySource, detectFormat, guardRows, guardZip, limitBytes, peek, previewSheet, readSheet, resolveLimits, runImport, toByteStream, toErrorBody, writeTemplate, xlsxRows };
799
825
  //# sourceMappingURL=index.mjs.map
800
826
  //# sourceMappingURL=index.mjs.map