@dipanshuhandoo/excelio 0.0.0-stage → 1.1.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 ADDED
@@ -0,0 +1,42 @@
1
+ # Changelog
2
+
3
+ ## 1.1.0 (2026-11-06)
4
+
5
+ ### Added
6
+
7
+ - Sheet `data` accepts any iterable or async iterable (generators, database cursors,
8
+ object-mode streams) as well as arrays; iterable sheets are written inline with
9
+ output backlog bounded at about 8 MiB, so memory stays flat for large exports.
10
+ - `maxRowsPerSheet` write option: sheets continue on `"<name> (2)"`, `"<name> (3)"`, ….
11
+ - `sharedStrings: 'auto' | true | false` write option (`'auto'`: off when a workbook
12
+ has an iterable sheet).
13
+ - `totalRows` sheet hint for progress, and `sheetPart` in `beforeWrite` context and
14
+ write progress events (which now also carry `sheetName`).
15
+ - `SOURCE_ERROR` error code for failing row sources.
16
+ - `npm run test:memory` release gate.
17
+
18
+ ### Fixed
19
+
20
+ - Non-array `data` (for example a generator) silently wrote an empty sheet.
21
+ - Sheets over 1,048,575 rows produced files Excel cannot open; they now roll over.
22
+ - Failed writes to a file path left a partial file and an open handle; the file is
23
+ now removed. Failed writes to a caller's stream no longer end that stream.
24
+ - Early file errors (such as a missing directory) could hang the write.
25
+ - `batchSize: 0` silently disabled progress events.
26
+
27
+ ### Changed
28
+
29
+ - Invalid `batchSize` (not a positive integer) now throws `INVALID_SPEC`.
30
+ - Specs and write options are validated before worker routing.
31
+ - `SheetSpec.data` is optional in the type declarations. TypeScript code with an
32
+ exhaustive switch over `ExcelIoErrorCode` must handle `SOURCE_ERROR`.
33
+
34
+ ## 1.0.0 (2026-10-08)
35
+
36
+ - Package as `@dipanshuhandoo/excelio`, supporting Node.js 22 and 24 LTS.
37
+ - Add readable/minified ESM and CommonJS builds, worker bundles, and declarations.
38
+ - Preserve Buffer results across worker threads and keep stream inputs inline.
39
+ - Add installed-tarball consumer verification and explicit public npm commands.
40
+ - Add runnable use-case examples, including XLSX-to-JSON conversion, selection/cleanup
41
+ tooling, and CI coverage.
42
+ - Document XLSX API behavior, memory limitations, and release/support policies.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DipanshuHandoo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,438 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
1
+ # ExcelIO
2
+
3
+ `@dipanshuhandoo/excelio` converts JavaScript row objects to XLSX workbooks and
4
+ reads XLSX files into structured data. Rows can come from arrays or be streamed
5
+ from generators, database cursors, and paged APIs with bounded memory. It is a
6
+ Node.js library, not a browser library. Its only runtime dependency is ExcelJS.
7
+
8
+ ## Features
9
+
10
+ **Writing**
11
+
12
+ - One call writes one or many workbooks, each with multiple sheets, to a Buffer,
13
+ a file path, a writable stream, or a readable stream (`writeStream`).
14
+ - Sheet `data` can be an array, a sync iterable, or an async iterable (async
15
+ generator, MongoDB/Mongoose cursor, object-mode stream, paged API).
16
+ - Large exports: streamed rows with a file or stream target keep memory flat
17
+ (about 50 MB growth for 1,000,000 rows), with output backpressure built in.
18
+ - Automatic sheet rollover at Excel's row limit, or earlier with
19
+ `maxRowsPerSheet`: `Links`, `Links (2)`, `Links (3)` … keep columns, header,
20
+ and styling.
21
+ - Columns: explicit keys, headers, widths, types (`string`, `number`, `boolean`,
22
+ `date`), and Excel number formats, or keys inferred from the first row.
23
+ - Write-side coercion of numeric/boolean strings and dates; multiline and
24
+ multilingual text.
25
+ - Styling: bold header, frozen header row, auto-filter; workbook properties
26
+ (author, company, title, created, modified).
27
+ - Safe sheet names: forbidden characters replaced, 31-character limit,
28
+ duplicate suffixes.
29
+ - Shared-strings control (`sharedStrings`) to trade file size for memory.
30
+
31
+ **Reading**
32
+
33
+ - Reads a Buffer, file path, or readable stream into
34
+ `{ ok, data, errors, stats }`, covering all sheets with headers as keys.
35
+ - Options for header row, blank handling, trimming, and date output (ISO string,
36
+ Date, or epoch).
37
+ - Formula cells return cached values; rich text and hyperlinks return displayed
38
+ text.
39
+
40
+ **Transforms and validation**
41
+
42
+ - `beforeWrite` and `afterRead` row hooks with row context (workbook, sheet,
43
+ part, Excel row number).
44
+ - `validateRow` collects per-row errors (partial data accepted) or fails fast
45
+ with `onRowError: 'fail'`.
46
+
47
+ **Operations**
48
+
49
+ - Worker-thread offload for large plain jobs (`useWorker: 'auto' | true | false`,
50
+ configurable thresholds); hooks, streams, and iterables run inline
51
+ automatically.
52
+ - Progress callbacks (`onProgress`, `batchSize`) with sheet, part, and total row
53
+ information.
54
+ - Cancellation through `AbortSignal`; streamed sheets respond during the write.
55
+ - Failure cleanup: a failed file write removes the partial file, and a caller's
56
+ stream is not ended or destroyed; row-source errors are reported as
57
+ `SOURCE_ERROR` with row details.
58
+ - Typed `ExcelIoError` codes: `INVALID_SPEC`, `IO_ERROR`, `ABORTED`,
59
+ `WORKER_ERROR`, `ROW_VALIDATION`, `SOURCE_ERROR`.
60
+
61
+ **Packaging**
62
+
63
+ - ESM and CommonJS, readable and minified (`/min`) builds, worker bundles,
64
+ source maps, and TypeScript declarations.
65
+
66
+ | Use case | Example |
67
+ | --- | --- |
68
+ | In-memory round-trip | [01-buffer-roundtrip](https://github.com/DipanshuHandoo/excelio/blob/main/examples/01-buffer-roundtrip.mjs) |
69
+ | Styled multi-sheet report to a file | [02-sales-report](https://github.com/DipanshuHandoo/excelio/blob/main/examples/02-sales-report.mjs) |
70
+ | Validated import with row errors | [03-import-validation](https://github.com/DipanshuHandoo/excelio/blob/main/examples/03-import-validation.mjs) |
71
+ | Stream a workbook to a destination | [04-streaming](https://github.com/DipanshuHandoo/excelio/blob/main/examples/04-streaming.mjs) |
72
+ | Workers and concurrent exports | [05-workers-and-concurrency](https://github.com/DipanshuHandoo/excelio/blob/main/examples/05-workers-and-concurrency.mjs) |
73
+ | Progress and cancellation | [06-progress-and-cancellation](https://github.com/DipanshuHandoo/excelio/blob/main/examples/06-progress-and-cancellation.mjs) |
74
+ | CommonJS and minified builds | [07-commonjs-and-minified](https://github.com/DipanshuHandoo/excelio/blob/main/examples/07-commonjs-and-minified.cjs) |
75
+ | XLSX to JSON | [08-excel-to-json](https://github.com/DipanshuHandoo/excelio/blob/main/examples/08-excel-to-json.mjs) |
76
+ | Service export migration | [09-service-exports](https://github.com/DipanshuHandoo/excelio/blob/main/examples/09-service-exports.mjs) |
77
+ | Stream rows from a paged source with rollover | [10-streaming-rows](https://github.com/DipanshuHandoo/excelio/blob/main/examples/10-streaming-rows.mjs) |
78
+
79
+ ## Installation
80
+
81
+ ```sh
82
+ npm install @dipanshuhandoo/excelio
83
+ ```
84
+
85
+ Requires Node.js 22 or later. Node 22 and 24 LTS are the tested support matrix.
86
+ TypeScript consumers should install `typescript` and `@types/node` as development
87
+ dependencies; NodeNext resolution supports both import and require declarations.
88
+
89
+ ## Quick Start
90
+
91
+ ```js
92
+ import { excelio } from '@dipanshuhandoo/excelio';
93
+
94
+ const spec = [{
95
+ workbook: 'sales',
96
+ properties: { author: 'system', title: 'Sales Report' },
97
+ sheets: [{
98
+ sheet: 'Orders',
99
+ columns: [
100
+ { key: 'customer', header: 'Customer', width: 30 },
101
+ { key: 'amount', type: 'number', format: '#,##0.00' },
102
+ { key: 'date', type: 'date' }
103
+ ],
104
+ styling: { headerBold: true, freezeHeader: true, autoFilter: true },
105
+ data: [{ customer: 'Alice', amount: 100, date: new Date('2026-01-15') }]
106
+ }]
107
+ }];
108
+
109
+ const buffer = await excelio.write(spec);
110
+ await excelio.write(spec, { to: 'sales.xlsx' });
111
+ const result = await excelio.read(buffer);
112
+ console.log(result.data[0].sheets[0].data);
113
+ ```
114
+
115
+ Stream a large export straight from a source, without building an array:
116
+
117
+ ```js
118
+ async function* orders() {
119
+ for await (const doc of Order.find({}).lean().cursor({ batchSize: 1000 })) {
120
+ yield { customer: doc.customer, amount: doc.amount, date: doc.createdAt };
121
+ }
122
+ }
123
+
124
+ await excelio.write([{
125
+ workbook: 'orders',
126
+ sheets: [{ sheet: 'Orders', columns: spec[0].sheets[0].columns, data: orders() }]
127
+ }], { to: 'orders.xlsx', signal, onProgress: console.log });
128
+ ```
129
+
130
+ CommonJS exposes the same named exports:
131
+
132
+ ```js
133
+ const { excelio, ExcelIoError } = require('@dipanshuhandoo/excelio');
134
+ ```
135
+
136
+ Opt-in minified builds have the same API and include their own worker files:
137
+
138
+ ```js
139
+ import { excelio } from '@dipanshuhandoo/excelio/min';
140
+ // CommonJS: const { excelio } = require('@dipanshuhandoo/excelio/min');
141
+ ```
142
+
143
+ Minification reduces library code size, not ExcelJS's installed dependencies or
144
+ workbook-processing time. Both builds include source maps and declarations.
145
+
146
+ ## Runnable Examples
147
+
148
+ See the [example guide](https://github.com/DipanshuHandoo/excelio/blob/main/examples/README.md)
149
+ for buffer round-trips, styled multi-sheet reports, validated imports, streaming,
150
+ workers/concurrent exports, progress/cancellation, CommonJS/minified usage,
151
+ XLSX-to-JSON conversion, service export migration, and streamed rows with sheet
152
+ rollover.
153
+ Examples live in the repository and use public package exports.
154
+
155
+ From a checkout, run all cases or select one:
156
+
157
+ ```sh
158
+ npm run examples
159
+ npm run examples -- 03-import-validation
160
+ ```
161
+
162
+ The runner builds first, checks results, and cleans up temporary spreadsheets.
163
+ Run the file examples directly after building to keep their XLSX/JSON output:
164
+
165
+ ```sh
166
+ node examples/02-sales-report.mjs
167
+ node examples/04-streaming.mjs
168
+ node examples/08-excel-to-json.mjs
169
+ node examples/10-streaming-rows.mjs
170
+ ```
171
+
172
+ These save to the ignored example-output/ directory by default.
173
+
174
+ ## Options Reference
175
+
176
+ Common to `write`, `writeStream`, and `read`:
177
+
178
+ | Option | Default | Purpose |
179
+ | --- | --- | --- |
180
+ | `onProgress` | none | `(event) => void`, called every `batchSize` rows; writes also emit a final event |
181
+ | `batchSize` | 5000 | Progress interval; on write it must be a positive integer and also sets the backpressure/yield interval for streamed sheets |
182
+ | `signal` | none | `AbortSignal`; rejects with `ABORTED` |
183
+ | `logger` | none | `{ info?, warn?, error? }` |
184
+ | `useWorker` | `'auto'` | `'auto'`, `true`, or `false` |
185
+ | `workerThreshold` | `{ rows: 50_000, bytes: 10 MiB }` | Auto-mode offload thresholds |
186
+
187
+ Write only:
188
+
189
+ | Option | Default | Purpose |
190
+ | --- | --- | --- |
191
+ | `to` | none (returns Buffer/Buffer[]) | File path or writable stream |
192
+ | `beforeWrite` | none | `(row, ctx) => row`, synchronous transform |
193
+ | `maxRowsPerSheet` | 1,048,575 | Rows per worksheet before continuing on `"<name> (2)"` |
194
+ | `sharedStrings` | `'auto'` | `'auto'` (off when a workbook has a streamed sheet), `true`, or `false` |
195
+
196
+ Read only:
197
+
198
+ | Option | Default | Purpose |
199
+ | --- | --- | --- |
200
+ | `headerRow` | 1 | Row containing headers |
201
+ | `nullForBlank` | `true` | Empty cells become `null` |
202
+ | `trimStrings` | `true` | Trim string cells |
203
+ | `dateFormat` | `'iso'` | `'iso'`, `'date'`, or `'epoch'` |
204
+ | `afterRead` | none | `(row, ctx) => row`, synchronous transform |
205
+ | `validateRow` | none | `(row, ctx) => null \| { message, code?, column? }` |
206
+ | `onRowError` | `'collect'` | `'collect'` or `'fail'` |
207
+
208
+ Sheet spec: `sheet`, `columns`, `styling` (`headerBold`, `freezeHeader`,
209
+ `autoFilter`), `data` (array, iterable, or async iterable; optional), and
210
+ `totalRows` (progress hint for streamed sheets).
211
+
212
+ ## API
213
+
214
+ ### Writing
215
+
216
+ - `await excelio.write(spec)` returns a Buffer for one workbook or Buffer[] for
217
+ multiple workbooks.
218
+ - `await excelio.write(spec, { to: filePath })` writes one workbook and returns
219
+ the path. A writable stream target is also supported and returned unchanged.
220
+ - `excelio.writeStream(spec)` returns a readable stream synchronously, writes
221
+ only the first workbook, and runs inline. Consume it or pipe it to a destination;
222
+ handle asynchronous errors via the stream's error event or pipeline.
223
+ - A single `to` target cannot receive multiple workbooks. Omit it to receive
224
+ separate buffers, or write each workbook individually. No ZIP wrapper is added.
225
+
226
+ `spec` is a non-empty array of workbooks, each containing a non-empty `sheets`
227
+ array. Sheets contain `sheet`, optional `columns` and `styling`, and optional
228
+ `data`. Workbook properties support author, company, title, created, and modified.
229
+ The `workbook` field is logical metadata, not an automatic filename.
230
+
231
+ `data` accepts an array, any iterable (for example a `function*` generator), or
232
+ any async iterable (an `async function*`, an object-mode stream, or any async
233
+ iterable such as a database cursor; excelio has no database dependency). Omitted
234
+ or null `data` writes an empty sheet. Iterables are consumed once, so pass a
235
+ separate iterable per sheet; reusing one iterable object across sheets throws
236
+ `INVALID_SPEC` (the same array may be reused).
237
+
238
+ ```js
239
+ async function* rows() {
240
+ for await (const doc of collection.find({}).batchSize(1000)) {
241
+ yield { url: doc.url, status: doc.status };
242
+ }
243
+ }
244
+
245
+ await excelio.write([{
246
+ workbook: 'audit',
247
+ sheets: [{ sheet: 'Pages', columns: [{ key: 'url' }, { key: 'status' }], data: rows() }]
248
+ }], { to: 'audit.xlsx' });
249
+ ```
250
+
251
+ When excelio stops early (error or abort) it calls the iterator's `return()`,
252
+ which ends a `for await` loop in the generator and closes the underlying cursor.
253
+
254
+ Columns support key, header, width, type (`string`, `number`, `boolean`, `date`),
255
+ and Excel number-format string. Without explicit columns, keys are inferred from
256
+ the first row only (for iterables, the first row is peeked and still written).
257
+ Explicit columns are recommended for iterables: stable order and types. Width
258
+ defaults to 15. Sheet names replace forbidden Excel characters, truncate to 31
259
+ characters, and receive suffixes when duplicated. `logger.warn` reports truncation.
260
+
261
+ ### Large Exports
262
+
263
+ - Combine iterable `data` with `to` (a file path or writable stream). Buffer
264
+ output still collects the whole file in memory.
265
+ - Iterable sheets wait whenever about 8 MiB of produced XML is queued for
266
+ compression or the target, checking every `batchSize` rows. Compression is
267
+ slower than row production, so this matters for local files too.
268
+ - `sharedStrings: 'auto' | true | false` (default `'auto'`): the Excel
269
+ shared-strings table is on unless a workbook has an iterable sheet. On gives a
270
+ smaller file, but memory grows with the number of distinct strings (1,000,000
271
+ rows of distinct URLs added 1.4–2.1 GB). Off keeps memory flat and writes
272
+ plain strings as `t="str"` cells.
273
+ - `maxRowsPerSheet` (default and maximum 1,048,575, Excel's limit minus the
274
+ header) continues a sheet on `"<name> (2)"`, `"<name> (3)"`, … with the same
275
+ columns, header, and styling. It applies to arrays and iterables. Names are
276
+ allocated after every logical sheet's own name, so a continuation never takes
277
+ a name another sheet uses.
278
+ - `totalRows` on an iterable sheet is an optional hint used for progress
279
+ `totalRows`; without it, progress `totalRows` is undefined.
280
+ - Measured with exceljs 4.4.0 on Node 22 (Windows): 1,000,000 rows with six
281
+ distinct-string columns to a file grew RSS by about 50 MB and took 24–58 s
282
+ across runs; a slow 64 KB sink and two 200,000-row sheets stayed under 25 MB. The
283
+ bound relies on exceljs/archiver internals, so it is re-measured on every
284
+ exceljs upgrade (`npm run test:memory`).
285
+
286
+ ### Reading
287
+
288
+ ```js
289
+ const result = await excelio.read('sales.xlsx');
290
+ // Also accepts a Buffer or readable stream.
291
+ ```
292
+
293
+ The result contains:
294
+
295
+ ```js
296
+ {
297
+ ok: true,
298
+ data: [{ workbook: undefined, sheets: [{ sheet: 'Orders', columns: [], data: [] }] }],
299
+ errors: [],
300
+ stats: { totalRows: 0, totalSheets: 1, durationMs: 0 }
301
+ }
302
+ ```
303
+
304
+ Column entries contain `key` and `header`. Read data uses header text as object
305
+ keys, not the original write keys. A column written as `{ key: 'customer',
306
+ header: 'Customer' }` reads back as `{ Customer: 'Alice' }`. Keep header equal to
307
+ key for round-trippable field names. The original workbook label, properties,
308
+ column types, widths, and styling are not reconstructed in the read result.
309
+
310
+ Read options: `headerRow` (default 1), `nullForBlank` (true), `trimStrings` (true),
311
+ and `dateFormat` (`iso`, `date`, or `epoch`; default `iso`). Entirely blank rows
312
+ are skipped. Formula cells return cached results, not formula text; rich text
313
+ and hyperlinks return displayed text. Excel error values become null.
314
+ `totalRows` counts accepted rows after transformation and validation.
315
+
316
+ ### Transforms and Validation
317
+
318
+ ```js
319
+ const result = await excelio.read(buffer, {
320
+ afterRead: row => ({ ...row, email: row.email?.toLowerCase() }),
321
+ validateRow: row => row.amount < 0
322
+ ? { code: 'NEGATIVE_AMOUNT', message: 'Amount must be non-negative', column: 'amount' }
323
+ : null,
324
+ onRowError: 'collect'
325
+ });
326
+
327
+ await excelio.write(spec, {
328
+ beforeWrite: row => ({ ...row, amount: Number(row.amount) })
329
+ });
330
+ ```
331
+
332
+ Hooks are synchronous. They receive `(row, ctx)` where ctx contains workbookIndex,
333
+ workbookName, sheetIndex, sheetName, and the 1-based Excel rowNumber. On write,
334
+ ctx also has `sheetPart` (1, 2, … after `maxRowsPerSheet` rollover); sheetIndex
335
+ stays the spec index, sheetName is the actual worksheet name, and rowNumber
336
+ restarts at 2 on each continuation sheet. Read applies
337
+ afterRead before validateRow. Invalid rows are excluded from data and collected
338
+ in errors by default; `onRowError: 'fail'` throws on the first issue. A hook
339
+ returning null/undefined keeps the original row. Write transforms do not add
340
+ columns beyond the explicit or inferred column set.
341
+
342
+ ### Workers, Progress, and Cancellation
343
+
344
+ ```js
345
+ await excelio.write(spec, { useWorker: true });
346
+ await excelio.read(buffer, { useWorker: false });
347
+ await excelio.write(spec, { workerThreshold: { rows: 100_000, bytes: 50 * 1024 * 1024 } });
348
+ ```
349
+
350
+ `useWorker` defaults to `auto`. Writes offload at 50,000 total rows; reads offload
351
+ at 10 MiB for buffers or file paths. Threshold comparisons are inclusive.
352
+ Each offloaded operation creates one worker, then terminates it. There is no
353
+ reusable worker pool, concurrency cap, or parallel splitting of a workbook.
354
+ Submit independent operations concurrently only when memory and CPU permit.
355
+
356
+ Functions (`beforeWrite`, `afterRead`, `validateRow`, `onProgress`), `logger`,
357
+ `signal`, stream inputs, writable stream destinations, and iterable sheet data
358
+ force inline execution, even when useWorker is true (with iterable data,
359
+ `logger.warn` reports the ignored useWorker). Worker payloads must be structured-cloneable;
360
+ functions or unsupported values embedded in row data cannot be sent to workers.
361
+ Bundlers/serverless deployments must preserve the emitted entrypoint and its
362
+ sibling workers directory. Import the package normally rather than copying a
363
+ single bundled file.
364
+
365
+ `onProgress` receives phase and rowsProcessed plus available workbook/sheet
366
+ metadata; write events include sheetIndex, sheetName, and sheetPart. `batchSize`
367
+ defaults to 5,000 and must be a positive integer. Writes emit a final
368
+ notification; reads notify at batch boundaries.
369
+
370
+ `signal` is checked between rows and sheets. Iterable sheets check it on every
371
+ row and yield to the event loop every `batchSize` rows, so an abort fired from a
372
+ timer or request handler is observed during long iterable writes. A source stuck
373
+ inside `next()` cannot be interrupted by excelio; pass the same signal to the
374
+ source. Array sheets still run as a synchronous loop, and initial XLSX loading
375
+ and commit cannot be interrupted.
376
+
377
+ ### Values and Errors
378
+
379
+ Date columns accept Date, ISO date strings, and epoch milliseconds. Number
380
+ columns convert finite numeric strings; boolean columns recognize true/false,
381
+ the strings 'true'/'false', and 1/0. Unsupported coercions are left unchanged;
382
+ these hints are not schema validation. Multiline and multilingual text is supported.
383
+
384
+ `ExcelIoError` exposes code, optional cause, and optional details. Codes include
385
+ INVALID_SPEC, IO_ERROR, ABORTED, WORKER_ERROR, ROW_VALIDATION, and SOURCE_ERROR.
386
+ SOURCE_ERROR means an iterable sheet's source threw (or returned a non-object
387
+ iterator result); `cause` is the original error and `details` holds
388
+ workbookIndex, sheetIndex, sheetName, and rowsRead. If the signal is already
389
+ aborted when the source fails, the error is ABORTED with the source error as
390
+ `cause`. Collected read
391
+ errors contain workbook index, sheet, row, column, code, and message. `ok` is
392
+ false when collected errors exist; callers decide whether to accept partial data.
393
+ Some lower-level ExcelJS/write errors can propagate as ordinary Error instances.
394
+
395
+ On a failed write, a file-path target is closed and removed; a caller's writable
396
+ stream is unpiped but neither ended nor destroyed; the original error is thrown,
397
+ with `err.cleanupError` set if cleanup itself failed. On success a caller's
398
+ stream is ended by the write, as before.
399
+
400
+ ## Memory and Scope
401
+
402
+ Writes use ExcelJS's streaming XLSX writer and commit rows incrementally. Array
403
+ `data` holds all rows in memory, and so does the shared-strings table when it is
404
+ on. Iterable `data` with the default `sharedStrings` and a file or stream target
405
+ keeps write memory bounded (see Large Exports). Buffer output collects the
406
+ complete file either way; direct file/stream output avoids that final buffer.
407
+ Reading currently loads the entire workbook and collects accepted rows.
408
+ Workers keep processing off the main thread but do not guarantee lower total
409
+ memory or faster completion; cloning large inputs adds overhead.
410
+
411
+ Browsers, legacy XLS, charts, pivots, macros, encryption, and advanced formatting
412
+ are outside this library's API. Only features described above are supported.
413
+
414
+ ## Development and Releases
415
+
416
+ ```sh
417
+ npm ci
418
+ npm run verify
419
+ npm run build:min
420
+ npm run test:perf
421
+ npm run pack:check
422
+ npm run publish:dry-run
423
+ ```
424
+
425
+ `build` produces dist/esm, dist/cjs, dist/esm-min, and dist/cjs-min with bundled
426
+ worker entrypoints and matching declarations. `build:min` regenerates only the
427
+ minified outputs; run a full build for a release. The only direct runtime
428
+ dependency is ExcelJS; esbuild, TypeScript, and Node types are development tools.
429
+
430
+ See [Contributing](https://github.com/DipanshuHandoo/excelio/blob/main/CONTRIBUTING.md),
431
+ [Releasing](https://github.com/DipanshuHandoo/excelio/blob/main/RELEASING.md),
432
+ [Security](https://github.com/DipanshuHandoo/excelio/blob/main/SECURITY.md), and
433
+ [CHANGELOG.md](CHANGELOG.md). Public npm availability
434
+ starts with the first approved publication; repository setup does not publish it.
435
+
436
+ ## License
437
+
438
+ MIT. See [LICENSE](LICENSE).