sheet-to-schema 1.1.0 → 1.2.0-rc.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/README.md CHANGED
@@ -14,7 +14,7 @@ one record per row, or per group of rows, reporting every cell it could not use.
14
14
  - **The same reader on both sides.** `sheet-to-schema/browser` reads a CSV, TSV or `.xlsx`
15
15
  file in a browser into exactly the rows the server reads from the same bytes.
16
16
 
17
- > **Status: stable (1.1.0).** It is the import engine of
17
+ > **Status: stable (1.1.0); 1.2.0-rc.1 on `next`.** It is the import engine of
18
18
  > [Dynamic Entity](https://github.com/berserker5619/Dynamic-Entity#readme) 3.0, extracted so it can be used without it.
19
19
  > Members marked `@internal` are not covered by semver and may change in any release.
20
20
 
@@ -29,7 +29,9 @@ npm install express # for the ready-made router
29
29
  ```
30
30
 
31
31
  Node 20 or later for the main entry; the three packages above are optional peers, loaded the
32
- first time they are needed. Reading a CSV needs none of them.
32
+ first time they are needed. Reading a CSV needs none of them, through the main entry's
33
+ `readCsvFile`. **`sheet-to-schema/browser` needs `exceljs` installed for a bundled app to
34
+ build**, even one that never reads a workbook: see the browser section.
33
35
 
34
36
  ## Quick start
35
37
 
@@ -174,7 +176,23 @@ For anything else, implement `SchemaAdapter`: `id`, `adapterVersion`, and
174
176
  its own cells (`coerce`), finish a record (`finalize`) and check one (`validate`). That is how
175
177
  Dynamic Entity's form configs run on this engine.
176
178
 
177
- ## In a browser: `sheet-to-schema/browser`
179
+ ## In a browser
180
+
181
+ **CSV only: `readCsvFile`, from the main entry.** It reads a CSV or TSV file exactly as
182
+ `readSheetFile` below does, with nothing in its module graph that asks for `exceljs`, so an app
183
+ that never installed it builds:
184
+
185
+ ```ts
186
+ import { applyMapping, decimalMarkFor, readCsvFile } from 'sheet-to-schema';
187
+
188
+ const sheet = await readCsvFile(file, { filename: file.name });
189
+ const result = applyMapping(sheet.rows, plan, adapter, undefined, { decimal: decimalMarkFor(sheet.delimiter) });
190
+ ```
191
+
192
+ The format is still decided by the bytes: an `.xlsx` (or any zip), a pre-2007 `.xls` and a
193
+ UTF-16 file are refused with `UNSUPPORTED_FORMAT`, with the server's messages for the last two.
194
+
195
+ **CSV and `.xlsx`: `sheet-to-schema/browser`.**
178
196
 
179
197
  ```ts
180
198
  import { applyMapping, decimalMarkFor } from 'sheet-to-schema';
@@ -192,9 +210,24 @@ messages. Pass `sheet.delimiter` to `decimalMarkFor`, as the server does, so a s
192
210
  reads `1,5` as 1.5 on both sides. `readXlsx` is the same for a caller that already knows the
193
211
  file is a workbook.
194
212
 
195
- - **A CSV needs nothing installed.** `exceljs` is loaded with `import()` the first time a
196
- workbook is read, so a CSV-only app never downloads it. Without it, a workbook fails with
197
- what to install.
213
+ - **Install `exceljs` to build with it.** It is loaded with `import()` the first time a
214
+ workbook is read, so a CSV-only user never downloads it, but a bundler still resolves that
215
+ `import()` when it builds. Without `exceljs` installed, checked in October 2026 (Dynamic
216
+ Entity's 3.1 spec, Step 0):
217
+
218
+ | Bundler | Builds? | Reading a workbook |
219
+ | --- | --- | --- |
220
+ | Angular 21 `ng build` | No: `Could not resolve "exceljs"` | — |
221
+ | Angular 21 `ng serve` | Yes | `PEER_MISSING` |
222
+ | Vite 8 (build and dev) | Yes | `PEER_MISSING` |
223
+ | webpack 5 | No: `Can't resolve 'exceljs'` | — |
224
+ | Any of them with `exceljs` marked external | Yes | `PEER_MISSING` (from 1.2.0; the browser's `TypeError` is its `cause`) |
225
+
226
+ A CSV read through `/browser` works wherever the app builds. An app that reads only CSV and
227
+ does not install `exceljs` uses `readCsvFile`.
228
+ - **A missing `exceljs` is `PEER_MISSING`** (an `ImportError`, from 1.2.0), with
229
+ `details.peer` and what to install. A workbook chunk that failed to download is not: the
230
+ browser's own error (a `TypeError`, or webpack's `ChunkLoadError`) passes through as it is.
198
231
  - **The limits are the server's**, with its defaults: `maxBytes`, `maxRows`, `maxColumns`,
199
232
  `maxCellLength` and `maxZipEntries`, through `limits`.
200
233
  - **A workbook's zip is checked by what it declares** (entry count, sizes, compression ratio)
@@ -264,7 +297,7 @@ createImportRouter(express.Router(), {
264
297
  });
265
298
  ```
266
299
 
267
- Four things to know before deploying it:
300
+ Five things to know before deploying it:
268
301
 
269
302
  1. **An import is not transactional.** A file that fails at row 30,000 has written 29,999
270
303
  records. Run `/validate` first, and make your writer idempotent. A grouped import can stop
@@ -279,6 +312,10 @@ Four things to know before deploying it:
279
312
  A preview lists `planVersions` and, from 1.1, `features` (`["group"]` on a router created
280
313
  with `grouping: true`); a missing `features` means the server will not group. The 1.0 form, `planForServer(plan, preview.planVersions)`,
281
314
  still works and checks plan versions only.
315
+ 5. **A 500 tells the client nothing, so the server is told instead.** From 1.2, `onError`
316
+ receives every failure the router answers with a 5xx, with its own code, such as
317
+ `PEER_MISSING` for a server without `exceljs`, answered `INTERNAL`, and the package. Without
318
+ `onError`, it is one `console.error` line and the error.
282
319
 
283
320
  ## Problem codes
284
321
 
@@ -288,15 +325,21 @@ Four things to know before deploying it:
288
325
  | `CELL_` | One cell could not be read. | `CELL_FORMAT`, `CELL_UNKNOWN_OPTION` |
289
326
  | `RECORD_` | The record built from a row is not valid, or needs a warning. | `RECORD_REQUIRED`, `RECORD_GROUP_CONFLICT` |
290
327
 
291
- `PLAN_PROBLEM_CODES`, `CELL_PROBLEM_CODES` and `RECORD_PROBLEM_CODES` list them all.
328
+ `PLAN_PROBLEM_CODES`, `CELL_PROBLEM_CODES` and `RECORD_PROBLEM_CODES` list them all. A
329
+ server's `PLAN_GROUP` refusal of a valid grouped plan also carries a `reason` (from 1.2.0):
330
+ `grouping-disabled` (the router was created without `grouping: true`) or `non-contiguous`
331
+ (`group.contiguous: false`, which the server never streams). Compare `reason`, never the
332
+ message.
292
333
 
293
334
  A grouped import can also stop as a whole, with an `ImportFailure` (`IMPORT_FAILURE_CODES`).
294
335
  `applyMapping` and `runImport` throw it; over HTTP it is the error body's `code`. And the
295
- browser reader can refuse to read a workbook at all, with an `ImportError`:
336
+ readers can refuse to read a file at all, with an `ImportError` (the main entry exports it from
337
+ 1.2.0, for `readCsvFile`):
296
338
 
297
339
  | Code | HTTP | Raised when |
298
340
  | --- | --- | --- |
299
341
  | `UNSUPPORTED_RUNTIME` | (browser only) | `sheet-to-schema/browser` is asked to read a workbook where there is no `DecompressionStream` with `deflate-raw` (see the browser section for versions). A CSV is not affected. |
342
+ | `PEER_MISSING` | (answered as `INTERNAL`, 500) | An optional peer the call needs is not installed: `exceljs` for a workbook, `busboy` for an upload. `details.peer` names it. A server's client is told only `INTERNAL`. |
300
343
  | `GROUP_NOT_CONTIGUOUS` | 422 | A group's key comes back after its rows ended. `details` has the key and both rows. |
301
344
  | `GROUP_TOO_LARGE` | 413 | A group has more than `maxGroupRows` rows. |
302
345
  | `TOO_MANY_GROUPS` | 413 | More than `maxGroups` groups, or more than `maxGroupKeyBytes` of group keys. |
@@ -1,6 +1,5 @@
1
- import { SheetGrid, CsvDelimiter } from 'sheet-to-schema';
2
- import { I as ImportLimits } from '../import-error-DkGXydP2.mjs';
3
- export { a as ImportError, b as ImportErrorCode } from '../import-error-DkGXydP2.mjs';
1
+ import { ImportLimits, SheetGrid, CsvDelimiter, SheetFileInput } from 'sheet-to-schema';
2
+ export { ImportError, ImportErrorCode, SheetFileInput } from 'sheet-to-schema';
4
3
 
5
4
  /** The limits a browser read enforces. Each one left out is the server's default. */
6
5
  type BrowserLimits = Pick<ImportLimits, 'maxBytes' | 'maxRows' | 'maxColumns' | 'maxCellLength' | 'maxZipEntries'>;
@@ -18,8 +17,7 @@ interface SheetFile extends SheetGrid {
18
17
  /** The separator a CSV turned out to use; pass it to `decimalMarkFor`. Absent for a workbook. */
19
18
  delimiter?: CsvDelimiter;
20
19
  }
21
- /** What a file can arrive as: a `File` or other `Blob`, or its bytes. */
22
- type SheetFileInput = Blob | ArrayBuffer | Uint8Array;
20
+
23
21
  /**
24
22
  * Read a CSV, TSV or `.xlsx` file into exactly the grid the server's `readSheet` reads from the
25
23
  * same bytes: the same headers, the same rows with the same typed cells, the same separator,
@@ -28,8 +26,8 @@ type SheetFileInput = Blob | ArrayBuffer | Uint8Array;
28
26
  * @throws ImportError `TOO_LARGE`, `SHEET_TOO_LARGE`, `UNSUPPORTED_FORMAT`, `MALFORMED_FILE`,
29
27
  * `ARCHIVE_REFUSED`, as the server would; and for a workbook, `UNSUPPORTED_RUNTIME` where the
30
28
  * runtime has no `DecompressionStream` with `deflate-raw` (before Node 20.12, Chrome or Edge
31
- * 103, Firefox 113, Safari 16.4). A workbook read without `exceljs` installed throws a
32
- * plain `Error` saying what to install.
29
+ * 103, Firefox 113, Safari 16.4). A workbook read without `exceljs` installed throws
30
+ * `PEER_MISSING`, saying what to install.
33
31
  */
34
32
  declare function readSheetFile(input: SheetFileInput, options?: ReadSheetFileOptions): Promise<SheetFile>;
35
33
  /**
@@ -40,4 +38,4 @@ declare function readSheetFile(input: SheetFileInput, options?: ReadSheetFileOpt
40
38
  */
41
39
  declare function readXlsx(input: SheetFileInput, options?: Omit<ReadSheetFileOptions, 'filename'>): Promise<SheetGrid>;
42
40
 
43
- export { type BrowserLimits, type ReadSheetFileOptions, type SheetFile, type SheetFileInput, readSheetFile, readXlsx };
41
+ export { type BrowserLimits, type ReadSheetFileOptions, type SheetFile, readSheetFile, readXlsx };
@@ -1,6 +1,5 @@
1
- import { SheetGrid, CsvDelimiter } from 'sheet-to-schema';
2
- import { I as ImportLimits } from '../import-error-DkGXydP2.js';
3
- export { a as ImportError, b as ImportErrorCode } from '../import-error-DkGXydP2.js';
1
+ import { ImportLimits, SheetGrid, CsvDelimiter, SheetFileInput } from 'sheet-to-schema';
2
+ export { ImportError, ImportErrorCode, SheetFileInput } from 'sheet-to-schema';
4
3
 
5
4
  /** The limits a browser read enforces. Each one left out is the server's default. */
6
5
  type BrowserLimits = Pick<ImportLimits, 'maxBytes' | 'maxRows' | 'maxColumns' | 'maxCellLength' | 'maxZipEntries'>;
@@ -18,8 +17,7 @@ interface SheetFile extends SheetGrid {
18
17
  /** The separator a CSV turned out to use; pass it to `decimalMarkFor`. Absent for a workbook. */
19
18
  delimiter?: CsvDelimiter;
20
19
  }
21
- /** What a file can arrive as: a `File` or other `Blob`, or its bytes. */
22
- type SheetFileInput = Blob | ArrayBuffer | Uint8Array;
20
+
23
21
  /**
24
22
  * Read a CSV, TSV or `.xlsx` file into exactly the grid the server's `readSheet` reads from the
25
23
  * same bytes: the same headers, the same rows with the same typed cells, the same separator,
@@ -28,8 +26,8 @@ type SheetFileInput = Blob | ArrayBuffer | Uint8Array;
28
26
  * @throws ImportError `TOO_LARGE`, `SHEET_TOO_LARGE`, `UNSUPPORTED_FORMAT`, `MALFORMED_FILE`,
29
27
  * `ARCHIVE_REFUSED`, as the server would; and for a workbook, `UNSUPPORTED_RUNTIME` where the
30
28
  * runtime has no `DecompressionStream` with `deflate-raw` (before Node 20.12, Chrome or Edge
31
- * 103, Firefox 113, Safari 16.4). A workbook read without `exceljs` installed throws a
32
- * plain `Error` saying what to install.
29
+ * 103, Firefox 113, Safari 16.4). A workbook read without `exceljs` installed throws
30
+ * `PEER_MISSING`, saying what to install.
33
31
  */
34
32
  declare function readSheetFile(input: SheetFileInput, options?: ReadSheetFileOptions): Promise<SheetFile>;
35
33
  /**
@@ -40,4 +38,4 @@ declare function readSheetFile(input: SheetFileInput, options?: ReadSheetFileOpt
40
38
  */
41
39
  declare function readXlsx(input: SheetFileInput, options?: Omit<ReadSheetFileOptions, 'filename'>): Promise<SheetGrid>;
42
40
 
43
- export { type BrowserLimits, type ReadSheetFileOptions, type SheetFile, type SheetFileInput, readSheetFile, readXlsx };
41
+ export { type BrowserLimits, type ReadSheetFileOptions, type SheetFile, readSheetFile, readXlsx };
@@ -31,127 +31,36 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
31
31
  mod
32
32
  ));
33
33
 
34
- // src/shared/import-error.ts
35
- var STATUS = {
36
- NO_FILE: 400,
37
- TOO_MANY_FILES: 400,
38
- TOO_LARGE: 413,
39
- UNSUPPORTED_FORMAT: 415,
40
- MALFORMED_FILE: 400,
41
- ARCHIVE_REFUSED: 400,
42
- SHEET_TOO_LARGE: 413,
43
- UNKNOWN_ENTITY: 404,
44
- INVALID_PLAN: 400,
45
- TIMEOUT: 408,
46
- IMPORT_FAILED: 500,
47
- // Not an HTTP answer: only the browser reader raises it. 501 if one ever has to be chosen.
48
- UNSUPPORTED_RUNTIME: 501,
49
- INTERNAL: 500
50
- };
51
- var ImportError = class extends Error {
52
- code;
53
- status;
54
- details;
55
- constructor(code, message, options = {}) {
56
- super(message);
57
- this.name = "ImportError";
58
- this.code = code;
59
- this.status = STATUS[code] ?? 500;
60
- this.details = options.details;
61
- if (options.cause !== void 0) this.cause = options.cause;
62
- }
63
- };
64
- var DEFAULT_LIMITS = {
65
- ...sheetToSchema.DEFAULT_GROUP_LIMITS,
66
- maxBytes: 10 * 1024 * 1024,
67
- maxFields: 16,
68
- maxFieldBytes: 1024 * 1024,
69
- maxFieldNameBytes: 200,
70
- maxFiles: 1,
71
- maxRows: 2e5,
72
- maxColumns: 512,
73
- maxCellLength: 32768,
74
- maxUncompressedBytes: 200 * 1024 * 1024,
75
- maxCompressionRatio: 200,
76
- maxZipEntries: 512,
77
- batchSize: 500,
78
- maxReportedErrors: 200,
79
- sampleRows: 5,
80
- idleTimeoutMs: 3e4,
81
- totalTimeoutMs: 10 * 6e4
82
- };
83
- function resolveLimits(overrides) {
84
- const out = { ...DEFAULT_LIMITS };
85
- if (!overrides) return out;
86
- for (const key of Object.keys(DEFAULT_LIMITS)) {
87
- const value = overrides[key];
88
- if (typeof value === "number" && Number.isFinite(value) && value > 0) {
89
- out[key] = Math.floor(value);
90
- }
91
- }
92
- return out;
93
- }
94
-
95
34
  // src/shared/optional.ts
96
35
  var MISSING = /* @__PURE__ */ new Set(["MODULE_NOT_FOUND", "ERR_MODULE_NOT_FOUND"]);
36
+ var quoted = (name) => name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
37
+ function bundlerMissing(name) {
38
+ const n = quoted(name);
39
+ return [
40
+ new RegExp(`^Could not resolve "${n}" imported by "`),
41
+ new RegExp(`^Failed to resolve module specifier ['"]${n}['"]`),
42
+ new RegExp(`^The specifier [\u201C"]${n}[\u201D"] was a bare specifier`),
43
+ new RegExp(`^Module name, ['"]${n}['"] does not resolve to a valid URL`)
44
+ ];
45
+ }
97
46
  function isMissing(error, name) {
98
47
  if (typeof error !== "object" || error === null) return false;
99
48
  const { code, message } = error;
100
- return MISSING.has(String(code)) && String(message).includes(`'${name}'`);
49
+ const text = String(message);
50
+ if (MISSING.has(String(code))) return text.includes(`'${name}'`);
51
+ return bundlerMissing(name).some((pattern) => pattern.test(text));
101
52
  }
102
53
  async function loadPeer(entry, name, neededTo, load) {
103
54
  try {
104
55
  return await load();
105
56
  } catch (error) {
106
57
  if (!isMissing(error, name)) throw error;
107
- throw new Error(
58
+ throw new sheetToSchema.ImportError(
59
+ "PEER_MISSING",
108
60
  `${entry} needs the "${name}" package to ${neededTo}, and it is not installed. It is an optional peer dependency: run \`npm install ${name}\`.`,
109
- { cause: error }
110
- );
111
- }
112
- }
113
- function guardHeaders(headers, limits) {
114
- if (headers.length > limits.maxColumns) {
115
- throw new ImportError("SHEET_TOO_LARGE", `The sheet has more than ${limits.maxColumns} columns.`);
116
- }
117
- }
118
- function guardRow(row, seen, width, limits) {
119
- if (seen > limits.maxRows) {
120
- throw new ImportError("SHEET_TOO_LARGE", `The sheet has more than ${limits.maxRows} rows.`);
121
- }
122
- if (row.length > limits.maxColumns) {
123
- throw new ImportError("SHEET_TOO_LARGE", `Row ${seen + 1} has more than ${limits.maxColumns} columns.`);
124
- }
125
- for (const cell of row) {
126
- if (typeof cell === "string" && cell.length > limits.maxCellLength) {
127
- throw new ImportError("SHEET_TOO_LARGE", `A cell in row ${seen + 1} is longer than ${limits.maxCellLength} characters.`);
128
- }
129
- }
130
- return sheetToSchema.padRow(row, width);
131
- }
132
-
133
- // src/shared/sheet-format.ts
134
- var MAGIC_BYTES = 8;
135
- var ZIP = [80, 75];
136
- var OLE2 = [208, 207, 17, 224];
137
- var UTF16_LE = [255, 254];
138
- var UTF16_BE = [254, 255];
139
- var startsWith = (bytes, signature) => signature.every((byte, i) => bytes[i] === byte);
140
- function detectFormat(head) {
141
- if (startsWith(head, ZIP)) return "xlsx";
142
- if (startsWith(head, OLE2)) {
143
- throw new ImportError(
144
- "UNSUPPORTED_FORMAT",
145
- "This is a pre-2007 Excel file (.xls). Save it as .xlsx or .csv and try again."
61
+ { cause: error, details: { peer: name } }
146
62
  );
147
63
  }
148
- if (startsWith(head, UTF16_LE) || startsWith(head, UTF16_BE)) {
149
- throw new ImportError(
150
- "UNSUPPORTED_FORMAT",
151
- "This file is UTF-16 encoded. Save it as UTF-8 CSV and try again."
152
- );
153
- }
154
- return "csv";
155
64
  }
156
65
 
157
66
  // src/shared/xlsx-cells.ts
@@ -273,7 +182,7 @@ function assertDeflateRaw() {
273
182
  try {
274
183
  new DecompressionStream("deflate-raw");
275
184
  } catch (cause) {
276
- throw new ImportError("UNSUPPORTED_RUNTIME", UNSUPPORTED_RUNTIME_MESSAGE, { cause });
185
+ throw new sheetToSchema.ImportError("UNSUPPORTED_RUNTIME", UNSUPPORTED_RUNTIME_MESSAGE, { cause });
277
186
  }
278
187
  }
279
188
  var ignore = () => void 0;
@@ -290,13 +199,13 @@ async function inflate(data, max) {
290
199
  size += next.value.length;
291
200
  if (size > max) {
292
201
  await reader.cancel();
293
- throw new ImportError("ARCHIVE_REFUSED", `The workbook inflates to more than the ${max} byte limit.`);
202
+ throw new sheetToSchema.ImportError("ARCHIVE_REFUSED", `The workbook inflates to more than the ${max} byte limit.`);
294
203
  }
295
204
  chunks.push(next.value);
296
205
  }
297
206
  } catch (error) {
298
- if (error instanceof ImportError) throw error;
299
- throw new ImportError("MALFORMED_FILE", "This is not a readable workbook.", { cause: error });
207
+ if (error instanceof sheetToSchema.ImportError) throw error;
208
+ throw new sheetToSchema.ImportError("MALFORMED_FILE", "This is not a readable workbook.", { cause: error });
300
209
  }
301
210
  return concat(chunks);
302
211
  }
@@ -327,7 +236,7 @@ function sheetNumber(name) {
327
236
  return match ? Number(match[1]) : null;
328
237
  }
329
238
  var refuse = (message) => {
330
- throw new ImportError("ARCHIVE_REFUSED", message);
239
+ throw new sheetToSchema.ImportError("ARCHIVE_REFUSED", message);
331
240
  };
332
241
  var CRC_TABLE = (() => {
333
242
  const table = new Int32Array(256);
@@ -369,12 +278,12 @@ var PartPicker = class {
369
278
  ordered() {
370
279
  if (!this.sheet) {
371
280
  if (!this.kept.has("xl/workbook.xml")) {
372
- throw new ImportError(
281
+ throw new sheetToSchema.ImportError(
373
282
  "UNSUPPORTED_FORMAT",
374
283
  "This is a zip archive that is not an Excel workbook. Upload an .xlsx or .csv file."
375
284
  );
376
285
  }
377
- throw new ImportError("MALFORMED_FILE", "This workbook has no worksheet.");
286
+ throw new sheetToSchema.ImportError("MALFORMED_FILE", "This workbook has no worksheet.");
378
287
  }
379
288
  return [...WANTED, this.sheet.name].map((name) => this.kept.get(name)).filter((entry) => entry !== void 0);
380
289
  }
@@ -424,7 +333,7 @@ function repack(entries) {
424
333
  return out;
425
334
  }
426
335
  var malformed = () => {
427
- throw new ImportError("MALFORMED_FILE", "The workbook could not be read.");
336
+ throw new sheetToSchema.ImportError("MALFORMED_FILE", "The workbook could not be read.");
428
337
  };
429
338
  function pickParts(bytes, limits) {
430
339
  const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
@@ -490,69 +399,20 @@ function pickParts(bytes, limits) {
490
399
  // src/browser/read-sheet-file.ts
491
400
  var loadExcel = () => loadPeer("sheet-to-schema/browser", "exceljs", "read .xlsx files", async () => (await Promise.resolve().then(() => __toESM(__require("exceljs")))).default);
492
401
  async function readSheetFile(input, options = {}) {
493
- const limits = resolveLimits(options.limits);
494
- const bytes = await bytesOf(input, limits.maxBytes);
495
- const format = detectFormat(bytes.subarray(0, MAGIC_BYTES));
402
+ const limits = sheetToSchema.resolveLimits(options.limits);
403
+ const bytes = await sheetToSchema.bytesOf(input, limits.maxBytes, "readSheetFile");
404
+ const format = sheetToSchema.detectFormat(bytes.subarray(0, sheetToSchema.MAGIC_BYTES));
496
405
  if (format === "xlsx") return { format, ...await xlsxGrid(bytes, limits) };
497
- return csvGrid(bytes, limits, /\.tsv$/i.test(options.filename ?? "") ? " " : void 0);
406
+ return sheetToSchema.csvGrid(bytes, limits, options.filename);
498
407
  }
499
408
  async function readXlsx(input, options = {}) {
500
- const limits = resolveLimits(options.limits);
501
- const bytes = await bytesOf(input, limits.maxBytes);
502
- if (detectFormat(bytes.subarray(0, MAGIC_BYTES)) !== "xlsx") {
503
- throw new ImportError("UNSUPPORTED_FORMAT", "This is not an .xlsx workbook. Read a CSV with readSheetFile.");
409
+ const limits = sheetToSchema.resolveLimits(options.limits);
410
+ const bytes = await sheetToSchema.bytesOf(input, limits.maxBytes, "readSheetFile");
411
+ if (sheetToSchema.detectFormat(bytes.subarray(0, sheetToSchema.MAGIC_BYTES)) !== "xlsx") {
412
+ throw new sheetToSchema.ImportError("UNSUPPORTED_FORMAT", "This is not an .xlsx workbook. Read a CSV with readSheetFile.");
504
413
  }
505
414
  return xlsxGrid(bytes, limits);
506
415
  }
507
- var tooLarge = (maxBytes) => {
508
- throw new ImportError("TOO_LARGE", `The file is larger than the ${maxBytes} byte limit.`);
509
- };
510
- async function bytesOf(input, maxBytes) {
511
- if (ArrayBuffer.isView(input)) {
512
- if (input.byteLength > maxBytes) tooLarge(maxBytes);
513
- return new Uint8Array(input.buffer, input.byteOffset, input.byteLength);
514
- }
515
- if (Object.prototype.toString.call(input) === "[object ArrayBuffer]") {
516
- const buffer = input;
517
- if (buffer.byteLength > maxBytes) tooLarge(maxBytes);
518
- return new Uint8Array(buffer);
519
- }
520
- const blob = input;
521
- if (blob !== null && typeof blob === "object" && typeof blob.size === "number" && typeof blob.slice === "function") {
522
- if (blob.size > maxBytes) tooLarge(maxBytes);
523
- return new Uint8Array(await blobBuffer(blob));
524
- }
525
- throw new TypeError("readSheetFile reads a Blob (such as a File), an ArrayBuffer or a Uint8Array.");
526
- }
527
- function blobBuffer(blob) {
528
- if (typeof blob.arrayBuffer === "function") return blob.arrayBuffer();
529
- return new Promise((resolve, reject) => {
530
- const reader = new FileReader();
531
- reader.onload = () => resolve(reader.result);
532
- reader.onerror = () => reject(reader.error);
533
- reader.readAsArrayBuffer(blob);
534
- });
535
- }
536
- function csvGrid(bytes, limits, forced) {
537
- const text = new TextDecoder("utf-8").decode(bytes);
538
- const line = sheetToSchema.completeFirstLine(text);
539
- if (line === null && text.length > limits.maxCellLength * limits.maxColumns + limits.maxColumns) {
540
- throw new ImportError("SHEET_TOO_LARGE", "The header row is longer than any row may be.");
541
- }
542
- const delimiter = forced ?? sheetToSchema.detectDelimiter(line ?? text);
543
- const reader = sheetToSchema.createCsvReader({ delimiter });
544
- const { headers, rows } = guardGrid([...reader.push(text), ...reader.end()], limits);
545
- return { format: "csv", headers, rows, delimiter };
546
- }
547
- function guardGrid(raw, limits) {
548
- const iterator = raw[Symbol.iterator]();
549
- const first = iterator.next();
550
- const headers = first.done ? [] : first.value.map(sheetToSchema.cellText);
551
- guardHeaders(headers, limits);
552
- const rows = [];
553
- for (let next = iterator.next(); !next.done; next = iterator.next()) rows.push(guardRow(next.value, rows.length + 1, headers.length, limits));
554
- return { headers, rows };
555
- }
556
416
  async function xlsxGrid(bytes, limits) {
557
417
  assertDeflateRaw();
558
418
  const parts = pickParts(bytes, limits);
@@ -563,13 +423,13 @@ async function xlsxGrid(bytes, limits) {
563
423
  try {
564
424
  await workbook.xlsx.load(archive, { ignoreNodes: IGNORED_NODES });
565
425
  } catch (error) {
566
- throw new ImportError("MALFORMED_FILE", "This is not a readable workbook.", { cause: error });
426
+ throw new sheetToSchema.ImportError("MALFORMED_FILE", "This is not a readable workbook.", { cause: error });
567
427
  }
568
428
  const sheet = workbook.worksheets[0];
569
429
  if (!sheet || sheet.rowCount === 0) {
570
- throw new ImportError("MALFORMED_FILE", "This workbook has no readable rows.");
430
+ throw new sheetToSchema.ImportError("MALFORMED_FILE", "This workbook has no readable rows.");
571
431
  }
572
- return guardGrid(sheetRows(sheet, limits), limits);
432
+ return sheetToSchema.guardGrid(sheetRows(sheet, limits), limits);
573
433
  }
574
434
  async function withoutFormulas(sheet, limits) {
575
435
  const xml = sheet.method === STORED ? sheet.data : await inflate(sheet.data, limits.maxUncompressedBytes);
@@ -604,7 +464,7 @@ function* sheetRows(sheet, limits) {
604
464
  const row = sheet.findRow(number);
605
465
  if (!row) continue;
606
466
  if (number - 1 > limits.maxRows) {
607
- throw new ImportError("SHEET_TOO_LARGE", `The sheet has more than ${limits.maxRows} rows.`);
467
+ throw new sheetToSchema.ImportError("SHEET_TOO_LARGE", `The sheet has more than ${limits.maxRows} rows.`);
608
468
  }
609
469
  while (expected < number) {
610
470
  yield [];
@@ -619,7 +479,10 @@ function* sheetRows(sheet, limits) {
619
479
  }
620
480
  }
621
481
 
622
- exports.ImportError = ImportError;
482
+ Object.defineProperty(exports, "ImportError", {
483
+ enumerable: true,
484
+ get: function () { return sheetToSchema.ImportError; }
485
+ });
623
486
  exports.readSheetFile = readSheetFile;
624
487
  exports.readXlsx = readXlsx;
625
488
  //# sourceMappingURL=index.js.map