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 +52 -9
- package/dist/browser/index.d.mts +6 -8
- package/dist/browser/index.d.ts +6 -8
- package/dist/browser/index.js +40 -177
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/index.mjs +22 -161
- package/dist/browser/index.mjs.map +1 -1
- package/dist/index.d.mts +276 -2
- package/dist/index.d.ts +276 -2
- package/dist/index.js +183 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +173 -2
- package/dist/index.mjs.map +1 -1
- package/dist/node/index.d.mts +21 -14
- package/dist/node/index.d.ts +21 -14
- package/dist/node/index.js +96 -158
- package/dist/node/index.js.map +1 -1
- package/dist/node/index.mjs +43 -114
- package/dist/node/index.mjs.map +1 -1
- package/package.json +4 -2
- package/dist/import-error-DkGXydP2.d.mts +0 -171
- package/dist/import-error-DkGXydP2.d.ts +0 -171
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)
|
|
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
|
|
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
|
-
- **
|
|
196
|
-
workbook is read, so a CSV-only
|
|
197
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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. |
|
package/dist/browser/index.d.mts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { SheetGrid, CsvDelimiter } from 'sheet-to-schema';
|
|
2
|
-
|
|
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
|
-
|
|
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
|
|
32
|
-
*
|
|
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,
|
|
41
|
+
export { type BrowserLimits, type ReadSheetFileOptions, type SheetFile, readSheetFile, readXlsx };
|
package/dist/browser/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { SheetGrid, CsvDelimiter } from 'sheet-to-schema';
|
|
2
|
-
|
|
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
|
-
|
|
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
|
|
32
|
-
*
|
|
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,
|
|
41
|
+
export { type BrowserLimits, type ReadSheetFileOptions, type SheetFile, readSheetFile, readXlsx };
|
package/dist/browser/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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,
|
|
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
|
|
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
|