@xlsxflow/core 1.1.3 → 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 CHANGED
@@ -1,5 +1,43 @@
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
+
17
+ ## 1.1.4
18
+
19
+ Fixes from the third round of end-user tests (700 cases).
20
+
21
+ ### Fixed
22
+
23
+ - `XlsxFlow.readFile` reads `.ods` files, and `getComments()` / `getImages()` work after reading an `.xlsx` with it.
24
+ - Control characters and U+FFFE/U+FFFF never reach the XML: cell text keeps them as `_xHHHH_` escapes, other text (document properties, notes' authors, validations, headers and footers, table columns, alt text, formulas) drops them, and sheet names with them are refused.
25
+ - `SheetWriter` stores formula text that starts with `#` as text, not as an error cell Excel repairs.
26
+ - `SheetWriter` waits while nobody reads its output, instead of pulling rows from the source without limit, and cancelling the output ends the row source (its `finally` runs).
27
+ - Cached formula results: formulas over other formula cells, scientific literals (`1E3`), reversed ranges, case-insensitive text comparison, values typed into `SUM`, 15-digit number text, `TRUE`/`FALSE` text, errors (`#DIV/0!`, `#VALUE!`) and circular references follow Excel. Streamed rows get no made-up cached values (in `OdsWriter` too).
28
+ - `SheetWriter` refuses overlapping or malformed merges, overlapping tables, table names that look like cell references, defined names over 255 characters, row options outside rows 1 to 1,048,576, list validations over 255 characters, truncated images, and picture sizes that are not positive numbers. Validation formulas lose a leading `=`, and every range of a multi-range print area names its sheet.
29
+ - `SheetEditor.appendSheet` writes dates, formulas, styles and NaN as `setCells` does, keeps both batches when called twice, writes rows in the sheet's namespace, and refuses rows past 1,048,576.
30
+ - `SheetEditor`: `setCells` edits the right row on sheets whose rows have no `r` attribute; a date and a style on one cell keep both; repeating a restyle reuses the formats it added; `insertRows` keeps ranges ending at the last row (`SUM(B1:B1048576)`), leaves references to other workbooks (`[1]S!A5`) alone, moves every cell of a shared formula on another sheet, and moves pivot tables on the sheet.
31
+ - `SheetReader`: entries whose data does not match their CRC-32, and worksheets cut off mid-row, are errors instead of silently different data. A zip comment holding the end-record signature, part names in another case, UTF-16 parts, rows with an unusable `r`, `t="d"` cells (now UTC ISO strings, formatted with their date format), sheet names with `&`, and a chart sheet as the first tab are read correctly.
32
+ - `sheetToJson` keeps values right of the header row (`Column3`); `streamToCsv` keeps blank rows between rows.
33
+ - `parseCsv` keeps numbers that overflow a double (`1e400`) as text.
34
+ - `OdsWriter` keeps the milliseconds of dates and converts whole-column references (`SUM(C:C)`); the `.ods` reader reads the created date as UTC.
35
+ - ZIP: UTF-8 entry names set the UTF-8 flag, 65,535 entries are refused (readers take 0xFFFF as ZIP64), and an encrypted or `.xls` file given where a ZIP is expected says so.
36
+
37
+ ### Added
38
+
39
+ - The `errors` parse option: `row.errors` marks cells holding error values.
40
+
3
41
  ## 1.1.3
4
42
 
5
43
  ### Fixed
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+, Bun, browsers and Cloudflare Workers (without `nodejs_compat`). Deno provides the same APIs but is not tested yet.
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`.
@@ -73,6 +73,7 @@ Dates come back as ISO-8601 strings. Opt in to more detail, each indexed like `r
73
73
  - `{ formulas: true }` gives `row.formulas`, with shared formulas expanded per cell.
74
74
  - `{ styles: true }` gives `row.styles`, as `CellStyle` objects (the same shape the writer takes). Theme and palette colours are resolved to ARGB.
75
75
  - `{ richText: true }` gives `row.richText`, the formatted runs of cells that have them. `row.cells` still holds the plain text.
76
+ - `{ errors: true }` gives `row.errors`, `true` for the cells that hold an error value such as `#N/A`. `row.cells` gives errors as text, so this tells them from text that reads `#N/A` (`.xlsx` only).
76
77
  - `{ formatted: true }` gives `row.formatted`, each cell's text as Excel (en-US) shows it: `1,234.50`, `25.6%`, `(42)`, `08-Oct-2026 2:05 PM`. It covers sections, conditions, dates and elapsed times, fractions, scientific notation, currency and text formats. Repeat fills (`*`) and colours are left out, and other locales are shown as en-US.
77
78
 
78
79
  `await reader.readWorkbook(createBlobReader(blob))` lists the sheets with their visibility, the defined names and the document properties, without reading any sheet.
@@ -88,11 +89,20 @@ Parts held in memory (workbook, shared strings, styles) are capped at 1 GiB unco
88
89
  In Node.js, read straight from disk with `XlsxFlow.readFile`, or pass `await createFileReader(path)` to any function that takes a reader:
89
90
 
90
91
  ```typescript
91
- import { XlsxFlow } from '@xlsxflow/core';
92
+ import { XlsxFlow, SheetReader, createFileReader } from '@xlsxflow/core';
92
93
 
93
94
  for await (const row of await XlsxFlow.readFile('./data.xlsx')) {
94
95
  console.log(row.cells);
95
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
+ }
96
106
  ```
97
107
 
98
108
  ### Writing an Excel File
@@ -180,7 +190,7 @@ editor.setCells('Sheet1', {
180
190
  A1: { style: { font: { bold: true }, fill: { type: 'solid', fgColor: 'FFFFFF00' } } }, // restyle, keep content
181
191
  B3: { value: 7, style: { numFmt: '0.00' } },
182
192
  });
183
- editor.appendSheet('Sheet1', [['new', 'row']]); // appended after the last existing row
193
+ editor.appendSheet('Sheet1', [['new', 'row']]); // after the last existing row (values, formulas, styles)
184
194
  editor.insertRows('Sheet1', 5, 3); // 3 empty rows before row 5
185
195
  editor.deleteRows('Sheet1', 20, 2); // rows 20-21
186
196
  editor.insertColumns('Sheet1', 'C'); // or deleteColumns('Sheet1', 'C', 2)
@@ -193,10 +203,10 @@ Edited cells keep their style. A style change is merged into the cell's current
193
203
 
194
204
  Macro-enabled workbooks (`.xlsm`) keep their VBA project and content type through every edit.
195
205
 
196
- `addSheet` takes an array of rows with values, formulas and styles; for hyperlinks, notes and sheet options, write the workbook with `SheetWriter`. `deleteSheet` removes names scoped to the sheet and turns other defined names that point at it into `#REF!`; formulas in other sheets that point at it are not rewritten. `insertRows`, `deleteRows`, `insertColumns` and `deleteColumns` move everything that points at the cells, as Excel does:
206
+ `addSheet` and `appendSheet` take arrays of rows with values, formulas and styles; for hyperlinks, notes and sheet options, write the workbook with `SheetWriter`. Calling `appendSheet` again for a sheet adds the rows after the earlier ones. `deleteSheet` removes names scoped to the sheet and turns other defined names that point at it into `#REF!`; formulas in other sheets that point at it are not rewritten. `insertRows`, `deleteRows`, `insertColumns` and `deleteColumns` move everything that points at the cells, as Excel does:
197
207
  - formulas on every sheet and the workbook's defined names (print areas, named ranges);
198
208
  - merged cells, conditional formats, validations, hyperlinks, the filter and its column filters, page breaks and column widths;
199
- - tables, pictures, notes, sparklines, What-If data tables, chart series and pivot-table sources.
209
+ - tables, pictures, notes, sparklines, What-If data tables, chart series, and pivot tables and their sources.
200
210
 
201
211
  Ranges that span inserted rows or columns grow, and ranges over deleted ones shrink. References to deleted cells become `#REF!`. Columns inserted inside a table become table columns named Column1, Column2 and so on. Deleting a table's header row, all its data rows or all its columns is refused. Operations run in the order given, and `setCells` addresses count after the cells have moved. Inserted rows and columns are empty: they don't copy the formatting of their neighbours. Every sheet streams through the editor, because any of its formulas might point at the moved cells.
202
212
 
@@ -242,7 +252,7 @@ writer.addSheet('Report', rows, {
242
252
  protection: { password: 'secret', sort: true }, // Excel's legacy hash: deters edits, is not encryption
243
253
  pageSetup: { orientation: 'landscape', paperSize: 9, fitToWidth: 1, fitToHeight: 0, printArea: 'A1:C100', printTitleRows: '1', footer: '&CPage &P of &N' },
244
254
  tabColor: 'FF00B050',
245
- mergeCells: ['A1:C1'],
255
+ mergeCells: ['E1:G1'], // not inside a table: Excel tables cannot hold merged cells
246
256
  columnWidths: [30, 12], // in characters; `columns[i].width` wins where both are set
247
257
  autoFitColumns: true, // widths from the longest value (array rows only)
248
258
  });
@@ -250,13 +260,13 @@ writer.addSheet('Report', rows, {
250
260
  // Formatted notes: comment: { text: [{ text: 'Ana:', font: { bold: true } }, { text: ' restated' }] }
251
261
  ```
252
262
 
253
- 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` and arithmetic), so other readers see a value.
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.
254
264
 
255
- Strings are written inline, which keeps memory flat. `new SheetWriter({ sharedStrings: true })` stores each distinct string once instead. Files are smaller when values repeat, but the distinct strings stay in memory until the file is finished.
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.
256
266
 
257
267
  ## Compared with SheetJS and ExcelJS
258
268
 
259
- Checked against each project's own documentation on 8 October 2026. "Pro" means a paid add-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.
260
270
 
261
271
  | | XlsxFlow | SheetJS Community Edition | ExcelJS 4.4 |
262
272
  |---|---|---|---|
@@ -267,7 +277,7 @@ Checked against each project's own documentation on 8 October 2026. "Pro" means
267
277
  | `.ods` | Read and write | Read and write | No |
268
278
  | `.xlsb`, `.numbers` and other formats | No | Yes | No |
269
279
  | Charts | Add (Pro) | No (SheetJS Pro) | No |
270
- | Pivot tables | Add (Pro) | No (SheetJS Pro) | Partial, undocumented |
280
+ | Pivot tables | Add (Pro) | No (SheetJS Pro) | No |
271
281
  | Password-protected files | Open and save (Pro) | Old `.xls` obfuscation only (SheetJS Pro opens AES files) | No |
272
282
  | Licence | MIT, Pro is paid | Apache 2.0 | MIT |
273
283