@xlsxflow/core 1.1.4 → 1.1.5
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/CHANGELOG.md +14 -0
- package/README.md +15 -6
- package/dist/index.cjs +45 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +45 -16
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.1.5
|
|
4
|
+
|
|
5
|
+
Fixes found while testing every claim in the README on Node 20.12 and 25, Bun, Deno, Chrome and Cloudflare Workers.
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- `SheetWriter`, `SheetEditor` and `OdsWriter` wait while nobody reads their output on Node and Bun too. Their `CompressionStream` takes thousands of chunks without pushing back, so a slow consumer let the writer pull the whole row source into memory.
|
|
10
|
+
- On Bun, `.ods` files and other ZIP entries stored without compression read correctly. Bun's `Blob.slice().stream()` runs past the end of the slice, which 1.1.4's size check rejected.
|
|
11
|
+
- `SheetWriter` refuses a merge that overlaps a table. Excel tables cannot hold merged cells, and Excel repaired such files by removing the table.
|
|
12
|
+
|
|
13
|
+
### Docs
|
|
14
|
+
|
|
15
|
+
- The README says to close a `createFileReader` reader (Node 25 stops the process when an open file is garbage-collected), lists Deno as tested, fixes the sheet-options example that merged cells inside a table, and corrects the comparison table: ExcelJS 4.4 has no pivot tables.
|
|
16
|
+
|
|
3
17
|
## 1.1.4
|
|
4
18
|
|
|
5
19
|
Fixes from the third round of end-user tests (700 cases).
|
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ Rows are read and written one at a time instead of loading the whole workbook, s
|
|
|
29
29
|
|
|
30
30
|
- **No dependencies**: TypeScript on Web APIs (`ReadableStream`, `CompressionStream`, `Blob`).
|
|
31
31
|
- **Streaming**: rows are read and written one at a time, so memory stays flat as files grow (10M cells written with about 1 MB of extra heap; see [Benchmarks](#benchmarks)).
|
|
32
|
-
- **Runs anywhere with Web APIs**: tested on Node 20.12
|
|
32
|
+
- **Runs anywhere with Web APIs**: tested on Node 20.12 and later, Bun, Deno, Chrome and Cloudflare Workers (without `nodejs_compat`).
|
|
33
33
|
- **Read, write and edit**: stream rows out of a file, generate one on the fly, or change cells, rows, columns and sheets of an existing file while keeping everything else in it.
|
|
34
34
|
- **Styles and formulas**: fonts, fills, borders, alignment, number formats, conditional formats, validations, tables, notes, hyperlinks, autofilters, images, protection and page setup. Formulas and styles read back too.
|
|
35
35
|
- **Older and open formats**: the same reader opens Excel 97-2003 `.xls` files and OpenDocument `.ods` files, and `OdsWriter` writes `.ods`.
|
|
@@ -89,11 +89,20 @@ Parts held in memory (workbook, shared strings, styles) are capped at 1 GiB unco
|
|
|
89
89
|
In Node.js, read straight from disk with `XlsxFlow.readFile`, or pass `await createFileReader(path)` to any function that takes a reader:
|
|
90
90
|
|
|
91
91
|
```typescript
|
|
92
|
-
import { XlsxFlow } from '@xlsxflow/core';
|
|
92
|
+
import { XlsxFlow, SheetReader, createFileReader } from '@xlsxflow/core';
|
|
93
93
|
|
|
94
94
|
for await (const row of await XlsxFlow.readFile('./data.xlsx')) {
|
|
95
95
|
console.log(row.cells);
|
|
96
96
|
}
|
|
97
|
+
|
|
98
|
+
// createFileReader keeps the file open until you close it, after you are done with
|
|
99
|
+
// everything read through it (Node 25 stops the process when an open file is garbage-collected)
|
|
100
|
+
const file = await createFileReader('./data.xlsx');
|
|
101
|
+
try {
|
|
102
|
+
console.log((await new SheetReader().readWorkbook(file)).sheets);
|
|
103
|
+
} finally {
|
|
104
|
+
await file.close();
|
|
105
|
+
}
|
|
97
106
|
```
|
|
98
107
|
|
|
99
108
|
### Writing an Excel File
|
|
@@ -243,7 +252,7 @@ writer.addSheet('Report', rows, {
|
|
|
243
252
|
protection: { password: 'secret', sort: true }, // Excel's legacy hash: deters edits, is not encryption
|
|
244
253
|
pageSetup: { orientation: 'landscape', paperSize: 9, fitToWidth: 1, fitToHeight: 0, printArea: 'A1:C100', printTitleRows: '1', footer: '&CPage &P of &N' },
|
|
245
254
|
tabColor: 'FF00B050',
|
|
246
|
-
mergeCells: ['
|
|
255
|
+
mergeCells: ['E1:G1'], // not inside a table: Excel tables cannot hold merged cells
|
|
247
256
|
columnWidths: [30, 12], // in characters; `columns[i].width` wins where both are set
|
|
248
257
|
autoFitColumns: true, // widths from the longest value (array rows only)
|
|
249
258
|
});
|
|
@@ -253,11 +262,11 @@ writer.addSheet('Report', rows, {
|
|
|
253
262
|
|
|
254
263
|
Formulas are stored for Excel to calculate when it opens the file. For array rows, the writer also stores a cached result for simple formulas (`SUM`, `AVERAGE`, `COUNT`, `MIN`, `MAX`, `IF`, `CONCATENATE`, `&`, comparisons and arithmetic, including over other formula cells), so other readers see a value. Errors are stored as error values (`#DIV/0!`). Rows from an AsyncIterable get no cached results, since the writer cannot look back at them.
|
|
255
264
|
|
|
256
|
-
Strings are written inline, which keeps memory flat. `new SheetWriter({ sharedStrings: true })` stores each distinct string once instead. Files are smaller when
|
|
265
|
+
Strings are written inline, which keeps memory flat. `new SheetWriter({ sharedStrings: true })` stores each distinct string once instead. Files are smaller when many different strings repeat (with only a handful of distinct values, inline strings compress as well or better), but the distinct strings stay in memory until the file is finished.
|
|
257
266
|
|
|
258
267
|
## Compared with SheetJS and ExcelJS
|
|
259
268
|
|
|
260
|
-
Checked against each project's own documentation on
|
|
269
|
+
Checked against each project's own documentation, and ExcelJS 4.4.0's published code, on 9 October 2026. "Pro" means a paid add-on.
|
|
261
270
|
|
|
262
271
|
| | XlsxFlow | SheetJS Community Edition | ExcelJS 4.4 |
|
|
263
272
|
|---|---|---|---|
|
|
@@ -268,7 +277,7 @@ Checked against each project's own documentation on 8 October 2026. "Pro" means
|
|
|
268
277
|
| `.ods` | Read and write | Read and write | No |
|
|
269
278
|
| `.xlsb`, `.numbers` and other formats | No | Yes | No |
|
|
270
279
|
| Charts | Add (Pro) | No (SheetJS Pro) | No |
|
|
271
|
-
| Pivot tables | Add (Pro) | No (SheetJS Pro) |
|
|
280
|
+
| Pivot tables | Add (Pro) | No (SheetJS Pro) | No |
|
|
272
281
|
| Password-protected files | Open and save (Pro) | Old `.xls` obfuscation only (SheetJS Pro opens AES files) | No |
|
|
273
282
|
| Licence | MIT, Pro is paid | Apache 2.0 | MIT |
|
|
274
283
|
|
package/dist/index.cjs
CHANGED
|
@@ -58,21 +58,34 @@ class ZipStreamWriter {
|
|
|
58
58
|
// Stream data, tracking sizes and CRC32
|
|
59
59
|
let uncompressedSize = 0;
|
|
60
60
|
let crc = 0xffffffff;
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
61
|
+
const compressor = new CompressionStream('deflate-raw');
|
|
62
|
+
const writer = compressor.writable.getWriter();
|
|
63
|
+
const reader = compressor.readable.getReader();
|
|
64
|
+
const input = inputStream.getReader();
|
|
65
|
+
// Node's and Bun's CompressionStream accept thousands of writes without backpressure, so one chunk
|
|
66
|
+
// is fed at a time: a write resolves once the chunk is compressed, which waits while nobody reads
|
|
67
|
+
// the output. Rows are then only pulled as fast as the ZIP is consumed.
|
|
68
|
+
const feed = (async () => {
|
|
69
|
+
try {
|
|
70
|
+
while (true) {
|
|
71
|
+
const { done, value } = await input.read();
|
|
72
|
+
if (done)
|
|
73
|
+
break;
|
|
74
|
+
await this.roomInQueue();
|
|
75
|
+
uncompressedSize += value.length;
|
|
76
|
+
crc = crc32Update(crc, value);
|
|
77
|
+
await writer.write(value);
|
|
78
|
+
}
|
|
79
|
+
await writer.close();
|
|
69
80
|
}
|
|
70
|
-
|
|
81
|
+
catch (err) {
|
|
82
|
+
// Stop the source too (a row generator's finally runs, a database cursor closes)
|
|
83
|
+
await input.cancel(err).catch(() => { });
|
|
84
|
+
await writer.abort(err).catch(() => { });
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
87
|
+
})();
|
|
71
88
|
let compressedSize = 0;
|
|
72
|
-
const reader = inputStream
|
|
73
|
-
.pipeThrough(crcStream)
|
|
74
|
-
.pipeThrough(new CompressionStream('deflate-raw'))
|
|
75
|
-
.getReader();
|
|
76
89
|
try {
|
|
77
90
|
while (true) {
|
|
78
91
|
const { done, value } = await reader.read();
|
|
@@ -81,10 +94,12 @@ class ZipStreamWriter {
|
|
|
81
94
|
compressedSize += value.length;
|
|
82
95
|
await this.pushChunk(value);
|
|
83
96
|
}
|
|
97
|
+
await feed;
|
|
84
98
|
}
|
|
85
99
|
catch (err) {
|
|
86
|
-
// Stop the source too (a row generator's finally runs, a database cursor closes)
|
|
87
100
|
await reader.cancel(err).catch(() => { });
|
|
101
|
+
await input.cancel(err).catch(() => { });
|
|
102
|
+
await feed.catch(() => { });
|
|
88
103
|
throw err;
|
|
89
104
|
}
|
|
90
105
|
crc = (crc ^ 0xffffffff) >>> 0;
|
|
@@ -3858,6 +3873,10 @@ class SheetWriter {
|
|
|
3858
3873
|
const box = parseRange$1(table.ref, 'table range');
|
|
3859
3874
|
if (boxes.some(b => overlaps(b, box)))
|
|
3860
3875
|
throw new Error(`Table "${table.name}" overlaps another table on sheet "${sheet.name}".`);
|
|
3876
|
+
// Excel tables cannot hold merged cells: it repairs the file by removing the table
|
|
3877
|
+
const merge = sheet.options.mergeCells?.find(ref => overlaps(parseRange$1(ref, 'merge range'), box));
|
|
3878
|
+
if (merge)
|
|
3879
|
+
throw new Error(`Merge "${merge}" overlaps table "${table.name}" on sheet "${sheet.name}"; Excel tables cannot contain merged cells.`);
|
|
3861
3880
|
boxes.push(box);
|
|
3862
3881
|
const first = colIndex(m[1]);
|
|
3863
3882
|
const width = colIndex(m[3]) - first + 1;
|
|
@@ -4359,8 +4378,18 @@ function createBlobReader(blob) {
|
|
|
4359
4378
|
return new Uint8Array(await slice.arrayBuffer());
|
|
4360
4379
|
},
|
|
4361
4380
|
stream(offset, length) {
|
|
4362
|
-
|
|
4363
|
-
|
|
4381
|
+
// Bun's Blob.slice(start, end).stream() runs on to the end of the original blob, so stop at length
|
|
4382
|
+
let left = length;
|
|
4383
|
+
return blob.slice(offset, offset + length).stream().pipeThrough(new TransformStream({
|
|
4384
|
+
start(controller) { if (left <= 0)
|
|
4385
|
+
controller.terminate(); },
|
|
4386
|
+
transform(chunk, controller) {
|
|
4387
|
+
controller.enqueue(chunk.length > left ? chunk.subarray(0, left) : chunk);
|
|
4388
|
+
left -= chunk.length;
|
|
4389
|
+
if (left <= 0)
|
|
4390
|
+
controller.terminate();
|
|
4391
|
+
}
|
|
4392
|
+
}));
|
|
4364
4393
|
},
|
|
4365
4394
|
async close() { }
|
|
4366
4395
|
};
|