@justybase/spreadsheet-tasks 3.1.1 → 3.2.0

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
@@ -5,6 +5,84 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [3.2.0] - 2026-11-08
9
+
10
+ ### Added
11
+
12
+ - Shared writer core (`src/writers/core`) used by the Node and browser XLSX/XLSB
13
+ writers. The unpacked package parts are byte-identical between Node and browsers.
14
+ - `useSharedStrings: false` writer option for inline strings (four writers).
15
+ - `ExcelError` cell value and round-trip support for Excel error cells.
16
+ - `XlsxReader.openBuffer()` and `XlsbReader.openBuffer()` plus `readRow()`,
17
+ `readAll()` and `rows()` batch APIs.
18
+ - Isolated-process I/O benchmark (`npm run benchmark:io`) with a 3% regression
19
+ gate, and a self-contained Microsoft Excel COM conformance suite
20
+ (`EXCEL_COM=1 npm run test:excel-com`).
21
+ - Etap 2 cell formatting: `CellStyle`, `StyledCell`, `FormulaCell`, `RichText`
22
+ and style helpers. `FormattedCell` remains supported.
23
+ - XLSX styling: fonts, fills, borders, alignment, protection, custom number
24
+ formats and differential styles for conditional formatting.
25
+ - XLSX sheet features: formula cells with cached values, rich text, merged
26
+ cells, hyperlinks, data validation, conditional formatting and cell comments
27
+ (classic notes with VML).
28
+ - XLSB styling and formulas: a BIFF12 style registry and a curated formula
29
+ compiler with cached values; merged cells, hyperlinks and data validation.
30
+ - Conservative column autofit (header + up to 200 sample rows, capped at 200,
31
+ not narrower than xlspy/Excel AutoFit up to that cap) and explicit
32
+ `columnWidths` overrides in all four writers. Streaming writers delay the
33
+ sheet head until the first 200 data rows have been measured.
34
+ - `XlsxReader.getStyle()`, `getFormula()` and `getRichText()`; `XlsbReader.getStyle()`.
35
+
36
+ ### Changed
37
+
38
+ - `writeSheet` accepts a sheet-options object in place of the legacy
39
+ `doAutofilter` boolean while keeping the boolean overload backward compatible.
40
+ - `updateColWidthsFromRows` understands styled, formula and rich-text cells.
41
+ - Autofit is deliberately conservative so it is not narrower than
42
+ xlspy/Excel AutoFit up to the 200 cap: columns use the 8.43 minimum, calibrated
43
+ character costs (CJK/fullwidth, emoji, wide fonts, bold/italic and font
44
+ size), number/date-format display text, room for the autofilter button, the
45
+ 5/7-character cell padding, and a 200-character cap on the stored width. The
46
+ default fallback width is 9.14.
47
+ - Autofit also measures alignment indent, the longest line of wrapped text,
48
+ negative number-format sections, and Excel's 11-character General rendering
49
+ instead of the full JavaScript decimal expansion.
50
+ - `options.autofitRows` controls how many rows are measured (default 200;
51
+ `0` disables row sampling).
52
+ - XLSB column widths are written as full 1/256-character units instead of
53
+ floored integers.
54
+ - Explicit `columnWidths` values are validated to 0.5-255 characters.
55
+
56
+ ### Fixed
57
+
58
+ - XLSX reading no longer scans the rest of the sheet for every cell. The
59
+ formula lookup was unbounded, so a 20,000 x 10 sheet took minutes to read
60
+ instead of milliseconds; tag and value lookups are now bounded to the cell.
61
+ - XLSX numeric cells are parsed without allocating an intermediate string
62
+ (`parseFloat` is still used for exponents and long mantissas), restoring
63
+ read throughput close to the pre-Etap-2 reader.
64
+ - Dates with a time of day now use the datetime style (`numFmt` 22) in XLSX and
65
+ XLSB instead of the date-only style, so the time is no longer hidden.
66
+ - Streaming `writeRow()` now feeds autofit instead of leaving columns sized only
67
+ from the header and `sampleRows`.
68
+ - Long runs of accented Latin, Greek/Cyrillic and symbol characters are no
69
+ longer measured narrower than xlspy; a per-character exception table covers
70
+ the outliers.
71
+ - XLSB `BrtWsDim` now contains the real last row and an inclusive last column;
72
+ previously rows were always 0 and the column was one past the data.
73
+ - XLSB cells with an error value are read as `#DIV/0!`-style text instead of `null`.
74
+ - XLSX reader resolves optional `r`/`t`/`s` cell attributes from raw bytes
75
+ (previously attribute-less and typed cells could be misread).
76
+ - XLSB custom number formats no longer produce a corrupt styles part.
77
+
78
+ ### Limitations
79
+
80
+ - XLSB rich text, comments and conditional formatting are not emitted yet; the
81
+ writers throw a clear error instead of silently dropping them.
82
+ - XLSB formula support is a subset (literals, A1 references/ranges, operators
83
+ and common worksheet functions); unsupported syntax throws.
84
+ - `XlsbReader` does not reconstruct formula text; it returns cached values.
85
+
8
86
  ## [3.1.1] - 2026-10-24
9
87
 
10
88
  ### Changed
package/README.md CHANGED
@@ -28,7 +28,16 @@ High-performance TypeScript library for reading and writing Excel files in XLSB
28
28
  - **TypeScript First** — Full TypeScript support with type definitions
29
29
  - **Dual Format Support** — Read and write both XLSB and XLSX formats
30
30
  - **Lightweight Node Dependencies** — Small ZIP-focused runtime deps (`adm-zip`, `archiver`, `yauzl`); no heavy Excel framework
31
- - **Streaming Support** — Efficient memory usage for large files
31
+ - **Streaming Support** - Efficient memory usage for large files
32
+ - **Shared Writer Core** - Node and browser writers emit byte-identical package parts
33
+ - **Inline or Shared Strings** - Opt into inline strings for high-cardinality text
34
+ - **Buffer I/O** - `openBuffer()` reads a workbook from memory; `readAll()`/`rows()` batch APIs
35
+ - **Styles and Formats** - Fonts, fills, borders, alignment, protection and number formats (`CellStyle`)
36
+ - **Formulas** - Write formula cells with cached values (`FormulaCell`); XLSX and a curated XLSB formula subset
37
+ - **Sheet Features** - Merged cells, hyperlinks, data validation, comments and conditional formatting in XLSX; merges, hyperlinks and validation in XLSB
38
+ - **Rich Text** - Formatted text runs in XLSX shared and inline strings
39
+ - **Conservative Column Autofit** - Not narrower than xlspy/Excel AutoFit up to the 200 cap; explicit `columnWidths` override it
40
+ - **Excel Error Values** - Read and write `#DIV/0!`, `#N/A` and the other Excel errors
32
41
  - **Multiple Sheets** — Support for multiple worksheets per workbook
33
42
  - **Auto-filter** — Automatic filter headers support
34
43
  - **Excel COM Conversion** — Convert XLSB and XLSX through desktop Microsoft Excel on Windows
@@ -190,6 +199,19 @@ const writer = new XlsxWriter('fast.xlsx', { compressionLevel: 1 });
190
199
 
191
200
  Out-of-range values throw a `RangeError`.
192
201
 
202
+ ### Inline Strings
203
+
204
+ Writers use a shared-string table by default, which is fastest when text repeats.
205
+ For high-cardinality text (IDs, messages) inline strings can reduce peak memory:
206
+
207
+ ```typescript
208
+ const xlsx = new XlsxWriter('inline.xlsx', { useSharedStrings: false });
209
+ const xlsb = new XlsbWriter('inline.xlsb', { useSharedStrings: false });
210
+ ```
211
+
212
+ The browser writers accept the same option:
213
+ `new BrowserXlsxWriter({ useSharedStrings: false })`.
214
+
193
215
  ### Reading Excel Files
194
216
 
195
217
  ```typescript
@@ -215,6 +237,101 @@ await xlsbReader.open('data.xlsb');
215
237
  // ... same API as above
216
238
  ```
217
239
 
240
+ Batch and in-memory APIs avoid the per-row `await`/`getValue` loop:
241
+
242
+ ```typescript
243
+ const reader = new XlsxReader();
244
+ await reader.openBuffer(buffer); // ZIP bytes, e.g. from fetch() or fs.readFileSync()
245
+ await reader.selectSheet(0); // or a sheet name
246
+ const rows = await reader.readAll(); // CellValue[][] (header row included)
247
+
248
+ for await (const row of reader.rows()) { // streaming without holding the sheet
249
+ consume(row);
250
+ }
251
+
252
+ // Synchronous fast path after selectSheet(): the returned array is reused until the next call.
253
+ let row: CellValue[] | null;
254
+ while ((row = reader.readRow()) !== null) {
255
+ consume(row.slice()); // copy if you keep it
256
+ }
257
+ ```
258
+
259
+ Excel error cells are returned as their display text (`'#DIV/0!'`). To write an error,
260
+ wrap it in `ExcelError`:
261
+
262
+ ```typescript
263
+ import { ExcelError } from '@justybase/spreadsheet-tasks';
264
+ writer.writeSheet([[new ExcelError('#N/A'), 1, 2]]);
265
+ ```
266
+
267
+ ### Styles, Formulas and Sheet Features
268
+
269
+ Cells can carry a full style, a formula with a cached value, or rich text:
270
+
271
+ ```typescript
272
+ import { XlsxWriter, RichText } from '@justybase/spreadsheet-tasks';
273
+
274
+ const writer = new XlsxWriter('report.xlsx');
275
+ writer.addSheet('Report');
276
+ writer.writeSheet(
277
+ [
278
+ [
279
+ { value: 1234.5, style: { numFmt: '#,##0.00', font: { bold: true, color: 'FF0000' }, fill: { fgColor: 'FFFF00' } } },
280
+ { formula: 'SUM(A2:A3)', value: 1234.5 },
281
+ new RichText([
282
+ { text: 'red', style: { font: { color: 'FF0000' } } },
283
+ { text: ' plain' },
284
+ ]),
285
+ ],
286
+ [200, { formula: 'A2*2', value: 400 }, 'x'],
287
+ ],
288
+ ['Amount', 'Total', 'Note'],
289
+ {
290
+ merges: ['A4:B4'],
291
+ hyperlinks: [{ ref: 'C1', target: 'https://example.com', tooltip: 'Example' }],
292
+ dataValidations: [{ sqref: 'C2:C5', type: 'list', values: ['x', 'y'] }],
293
+ conditionalFormats: [{
294
+ sqref: 'A2:A3',
295
+ rules: [{ type: 'cellIs', operator: 'greaterThan', formula: ['1000'], style: { font: { bold: true } } }],
296
+ }],
297
+ comments: [{ ref: 'A2', text: 'Reviewed', author: 'Analyst' }],
298
+ columnWidths: [18, 14], // optional; otherwise conservative autofit is used
299
+ },
300
+ );
301
+ await writer.finalize();
302
+ ```
303
+
304
+ `columnWidths` wins over autofit. Autofit itself is deliberately conservative:
305
+ it looks at the header plus up to 200 sample rows (xlspy measures up to 1000) and
306
+ estimates widths from character costs calibrated against xlspy's widest font
307
+ metrics. The estimate includes CJK/fullwidth characters, emoji, bold/italic
308
+ text, font size, wide font families, common number/date formats, alignment
309
+ indent, the longest line of wrapped text and negative number-format sections.
310
+ It enforces Excel's 8.43 minimum, reserves room for the autofilter button, adds
311
+ the 5/7-character cell padding when writing, and caps the stored width at 200.
312
+ The estimate errs on the wide side so columns are not narrower than
313
+ xlspy/Excel AutoFit up to that cap. In streaming mode the sheet head is delayed
314
+ until the first 200 data rows have been measured. Explicit
315
+ `columnWidths` must be 0.5-255 characters and are written unchanged.
316
+
317
+ XLSB supports `CellStyle`, `FormulaCell` (a formula subset with cached values),
318
+ `merges`, `hyperlinks`, `dataValidations` and `columnWidths`. Rich text,
319
+ comments and conditional formatting are XLSX-only and throw a clear error when
320
+ passed to an XLSB writer.
321
+
322
+ XlsxReader exposes the style, formula and rich-text runs of the last read row:
323
+
324
+ ```typescript
325
+ const reader = new XlsxReader();
326
+ await reader.open('report.xlsx');
327
+ await reader.selectSheet('Report');
328
+ while (reader.readRow() !== null) {
329
+ const style = reader.getStyle(0); // CellStyle | null
330
+ const formula = reader.getFormula(1); // string | null (no leading '=')
331
+ const rich = reader.getRichText(2); // RichText | null
332
+ }
333
+ ```
334
+
218
335
  ### Updating Data in an Existing Workbook
219
336
 
220
337
  `XlsxUpdater` and `XlsbUpdater` replace the cell data of a worksheet inside an
@@ -343,8 +460,12 @@ npm test
343
460
  Before opening a pull request:
344
461
 
345
462
  1. Run `npm run lint` and `npm test`.
346
- 2. Add or update tests if you change behavior.
347
- 3. Update [CHANGELOG.md](./CHANGELOG.md) for user-visible changes.
463
+ 2. Run `npm run benchmark:io:baseline` once, then `npm run benchmark:io:check` to
464
+ catch I/O regressions (the quick gate defaults to 5% on a quiet machine; pass
465
+ `-- --max-regression 3` for the strict full-run gate).
466
+ 3. On Windows with desktop Excel, run `EXCEL_COM=1 npm run test:excel-com`.
467
+ 4. Add or update tests if you change behavior.
468
+ 5. Update [CHANGELOG.md](./CHANGELOG.md) for user-visible changes.
348
469
 
349
470
  Please open an issue or pull request on [GitHub](https://github.com/justybase/justybase_spreadsheet_tasks).
350
471