Skip to content

fix(stream): read xlsx via central directory so streaming WorkbookReader stops losing the workbook entry#86

Open
amir-arad-lmnd wants to merge 1 commit into
protobi:masterfrom
amir-arad-lmnd:fix/streaming-workbook-reader-central-directory
Open

fix(stream): read xlsx via central directory so streaming WorkbookReader stops losing the workbook entry#86
amir-arad-lmnd wants to merge 1 commit into
protobi:masterfrom
amir-arad-lmnd:fix/streaming-workbook-reader-central-directory

Conversation

@amir-arad-lmnd

Copy link
Copy Markdown

Fixes the intermittent streaming-reader crash (upstream exceljs#3064): Cannot read properties of undefined (reading 'sheets') on Node >= 18.

Root cause

The old parse() used a single streaming unzip.Parse pass, emitting entries in stored order. Because xl/workbook.xml is typically written last, worksheets can be parsed before this.model is set. The guard only checked this.sharedStrings && this.workbookRels, not this.model, so this.model.sheets threw intermittently depending on ZIP entry ordering + async-iteration timing on Node >= 18.

Fix

Read the archive through its central directory (unzip.Open via _openZip) so every part is addressable. Dependency entries (rels, workbook.xml, sharedStrings, styles) are always parsed before any worksheet, guaranteeing this.model is present. Worksheets are still consumed lazily via iterateStream, so the full workbook is never held in memory.

Notes

  • exceljs-authored workbooks (sharedStrings/workbook.xml ordering) reliably trigger the race; real Excel-authored files usually don't, which is why it reads as an intermittent 'bad file' in production.
  • Adds regression spec spec/integration/issues/issue-3064-streaming-workbook-model.spec.js.
  • Original fix authored by @tiago-peixoto; cherry-picked onto protobi/master (conflict in parse() resolved in favor of the central-directory implementation).

…der stops losing the workbook entry

stream.xlsx.WorkbookReader intermittently threw "Cannot read properties of
undefined (reading 'sheets')" in _parseWorksheet on Node >= 18 (~90% of reads
on Node 22). parse() read the archive from a single streaming unzip pass in
stored order; because xl/workbook.xml is written last, that pass often did not
deliver it to _parseWorkbook before the worksheets were parsed, leaving
this.model undefined.

Read the archive through its central directory (unzipper.Open) instead: parse
rels/workbook/sharedStrings/styles before any worksheet, then emit worksheets
in order, still consuming each row-by-row so the workbook is never materialized
in memory. Removes the temp-file worksheet spooling and the tmp dependency usage.

Fixes exceljs#3064.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants