Reject non-ZIP input (e.g. a legacy binary .xls) with a stable error code
Problem
When a non-ZIP file is passed to readXlsxFile — most commonly a legacy binary .xls (an OLE2 Compound File, not a ZIP) — the error surfaced to the caller is an obscure low-level message leaked from the transitive unzip dependency, e.g. Error: invalid signature: 0xe011cfd0 (the file's first four bytes read little-endian). There's no stable, typed way to detect this, so consumers end up string-matching the message (Hyrum's law).
Change
Validate the input's leading bytes at the two zip-layer entry points and throw an InvalidInputError — a class exported from every entry point (/node, /browser, /universal, /web-worker) — carrying a documented, stable reason property, following the same convention as the existing schema-parsing InvalidError class:
XLS_NOT_SUPPORTED— OLE2/CFB signature (D0 CF 11 E0), i.e. a binary.xlsfileNOT_A_ZIP_ARCHIVE— any other input that isn't a ZIP archive
Anything starting with the ZIP signature (PK) passes through untouched. The class also sets name = 'InvalidInputError' so the error stays detectable via error.name in setups where instanceof is unreliable (ESM + CommonJS builds loaded together).
unzipFromArrayBufferUsingFunction(browser / universal / sync / web-worker) — sync check on the first bytes.unzipFromStream(Node) — a non-consuming stream peek thatunshifts the inspected bytes back, so the archive is still read in full; it accumulates across chunks.
Also included: TypeScript declarations (InvalidInputError, InvalidInputErrorReason), a README "Errors" section, and an InvalidInputError entry in the exports test.
Cost & tests
A ≤4-byte comparison — no measurable perf impact. Pure unit tests for the detector + integration tests through both entry points (including a stream delivered one byte at a time). No binary fixture committed — signatures are built in memory.