|
| 1 | +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. |
| 2 | + |
| 3 | +/** |
| 4 | + * The one place this package asserts around `exceljs`'s broken `load` signature |
| 5 | + * (#13378). Test layer only — nothing in `src/index.ts` reaches it, so tsup |
| 6 | + * (entry: `src/index.ts`) never emits it into `dist` and it is not published. |
| 7 | + * |
| 8 | + * ## Why the assertion below is unavoidable, in the dependency's own bytes |
| 9 | + * |
| 10 | + * `exceljs@4.4.0/index.d.ts` opens, at line 1, with: |
| 11 | + * |
| 12 | + * ```ts |
| 13 | + * declare interface Buffer extends ArrayBuffer { } |
| 14 | + * ``` |
| 15 | + * |
| 16 | + * That file carries 106 top-level `export` declarations, so it **is a module** — |
| 17 | + * which makes this `Buffer` module-local, and it therefore SHADOWS Node's global |
| 18 | + * `Buffer` inside every exceljs signature. The one that matters here is the |
| 19 | + * `Xlsx.load` at `index.d.ts:1490`: |
| 20 | + * |
| 21 | + * ```ts |
| 22 | + * load(buffer: Buffer, options?: Partial<XlsxReadOptions>): Promise<Workbook>; |
| 23 | + * ``` |
| 24 | + * |
| 25 | + * ⇒ `Workbook.xlsx.load` does not ask for a Node `Buffer`. It asks for something |
| 26 | + * structurally identical to `ArrayBuffer`. A Node `Buffer` is a `Uint8Array`, so |
| 27 | + * it is not assignable, and tsc says so precisely: |
| 28 | + * |
| 29 | + * ``` |
| 30 | + * src/rest.test.ts(1267,26): error TS2345: Argument of type 'Buffer<ArrayBuffer>' is not assignable to parameter of type 'Buffer'. |
| 31 | + * The types of 'slice(...)[Symbol.toStringTag]' are incompatible between these types. |
| 32 | + * Type '"Uint8Array"' is not assignable to type '"ArrayBuffer"'. |
| 33 | + * ``` |
| 34 | + * |
| 35 | + * ⭐ There is NO Node `Buffer` value that satisfies that parameter. The defect is |
| 36 | + * in the published declaration, not at any call site — so this is not laziness, |
| 37 | + * and no amount of care at a call site can remove it. What a call site CAN do is |
| 38 | + * not restate it: before #13378 the package paid this at 6 anonymous `as any`s |
| 39 | + * and left a 7th site as a ledgered `TS2345`. Now it is stated once, here. |
| 40 | + * |
| 41 | + * ## Why option C (upgrade) is not the answer — measured 2026-08-30 |
| 42 | + * |
| 43 | + * `exceljs` `dist-tags.latest` IS 4.4.0 (published 2023-10-19). The only publish |
| 44 | + * after it in the package's whole 166-version history is `4.4.1-prerelease.0` |
| 45 | + * (2024-12-20), and its `index.d.ts` carries the identical declaration at the |
| 46 | + * identical lines: shim at line 1, 106 exports, `load(buffer: Buffer, …)` at |
| 47 | + * 1490. The registry's `time.modified` is that same date. There is no later line |
| 48 | + * to pin to. (4.3.0 ships the shim too, so this is not a 4.4.0 regression that a |
| 49 | + * bump could undo.) |
| 50 | + * |
| 51 | + * ## Why not a declaration override (option B) |
| 52 | + * |
| 53 | + * The `Buffer` above is module-local, so it cannot be reached by interface |
| 54 | + * augmentation from outside; an override would have to redeclare the module, |
| 55 | + * replacing the package's entire typing surface. Far more surface than the one |
| 56 | + * assertion it would remove. |
| 57 | + * |
| 58 | + * ## Runtime is untouched |
| 59 | + * |
| 60 | + * exceljs's `load` has always accepted the Node `Buffer` these tests hand it — |
| 61 | + * that is what every one of those 6 `as any` sites was doing, and passing. This |
| 62 | + * helper changes only what tsc is told; it does not convert, copy or reshape the |
| 63 | + * bytes. |
| 64 | + */ |
| 65 | + |
| 66 | +import ExcelJS from 'exceljs'; |
| 67 | +import type { Workbook, Xlsx } from 'exceljs'; |
| 68 | + |
| 69 | +/** |
| 70 | + * The parameter type `Xlsx.load` actually declares, read off the dependency's |
| 71 | + * own signature instead of spelled by hand. Naming it this way means that if |
| 72 | + * exceljs ever drops the shim, this alias resolves to Node's `Buffer` and the |
| 73 | + * assertion below quietly becomes a no-op rather than a lie. |
| 74 | + */ |
| 75 | +type XlsxLoadInput = Parameters<Xlsx['load']>[0]; |
| 76 | + |
| 77 | +/** |
| 78 | + * Load .xlsx bytes into a fresh {@link Workbook}. |
| 79 | + * |
| 80 | + * The single site in this package where the exceljs declaration defect above is |
| 81 | + * asserted away. Callers pass the Node `Buffer` their fixture produced and get |
| 82 | + * back a workbook; no call site needs to know about any of this. |
| 83 | + */ |
| 84 | +export async function loadXlsxWorkbook(bytes: Buffer): Promise<Workbook> { |
| 85 | + const wb = new ExcelJS.Workbook(); |
| 86 | + await wb.xlsx.load(bytes as unknown as XlsxLoadInput); |
| 87 | + return wb; |
| 88 | +} |
0 commit comments