@dynamic-entity/server 2.2.0 → 2.3.1

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, forced) {
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 = forced ?? 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,12 @@ 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 forced = /\.tsv$/i.test(options.filename ?? "") ? " " : void 0;
615
+ const rows = format === "xlsx" ? xlsxRows(stream, limits) : csvRows(stream, limits, found, forced);
595
616
  const iterator = rows[Symbol.asyncIterator]();
596
617
  const first = await iterator.next();
597
- const headers = first.done ? [] : first.value.map(sampleText);
618
+ const headers = first.done ? [] : first.value.map(cellText);
598
619
  if (headers.length > limits.maxColumns) {
599
620
  throw new ImportError(
600
621
  "SHEET_TOO_LARGE",
@@ -605,7 +626,8 @@ async function readSheet(options) {
605
626
  return {
606
627
  format,
607
628
  headers,
608
- rows: closing(guardRows(remaining, headers.length, limits), options.stream)
629
+ rows: closing(guardRows(remaining, headers.length, limits), options.stream),
630
+ ...found.delimiter ? { delimiter: found.delimiter } : {}
609
631
  };
610
632
  } catch (error) {
611
633
  destroySource(options.stream);
@@ -657,6 +679,7 @@ async function runImport(options) {
657
679
  filename: options.filename,
658
680
  limits
659
681
  });
682
+ const decimal = options.decimal ?? decimalMarkFor(sheet.delimiter);
660
683
  const errors = [];
661
684
  let errorCount = 0;
662
685
  let imported = 0;
@@ -672,6 +695,7 @@ async function runImport(options) {
672
695
  batch = [];
673
696
  const result = applyMapping(rows, plan, config, {
674
697
  lang: options.lang,
698
+ decimal,
675
699
  rules: options.rules,
676
700
  lookups: options.lookups,
677
701
  firstRowNumber: firstRow
@@ -734,19 +758,22 @@ async function previewSheet(options) {
734
758
  try {
735
759
  for await (const row of sheet.rows) {
736
760
  rowCount++;
737
- if (sample.length < limits.sampleRows) sample.push(row.map(sampleText));
761
+ if (sample.length < limits.sampleRows) sample.push(row.map(cellText));
738
762
  }
739
763
  } catch (error) {
740
764
  destroySource(options.stream);
741
765
  throw error;
742
766
  }
743
- const { columns } = deriveImportColumns(options.config, { lang: options.lang });
767
+ const arrayBound = arrayBoundFor(sheet.headers, options.config, null, options.lang);
768
+ const { columns } = deriveImportColumns(options.config, { lang: options.lang, maxArrayRows: arrayBound });
744
769
  return {
745
770
  headers: sheet.headers,
746
771
  sample,
747
772
  rowCount,
748
773
  suggestion: suggestMapping(sheet.headers, columns, options.config?.entity ?? ""),
749
- format: sheet.format
774
+ format: sheet.format,
775
+ ...sheet.delimiter ? { delimiter: sheet.delimiter } : {},
776
+ arrayBound
750
777
  };
751
778
  }
752
779
  var TEMPLATE_EXTENSION = { csv: "csv", xlsx: "xlsx" };
@@ -795,6 +822,6 @@ function sheetName(name) {
795
822
  return (cleaned || "Import").slice(0, 31);
796
823
  }
797
824
 
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 };
825
+ 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
826
  //# sourceMappingURL=index.mjs.map
800
827
  //# sourceMappingURL=index.mjs.map