@justybase/spreadsheet-tasks 2.1.1 → 2.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
@@ -11,11 +11,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
11
11
 
12
12
  - `convertXlsbToXlsx` and `convertXlsxToXlsb` — Windows/Excel COM conversions
13
13
  with protected destinations and optional explicit overwriting.
14
+ - `replaceSheetDataStream()` and `saveStreaming()` for XLSX/XLSB updaters,
15
+ accepting synchronous or asynchronous row sources without collecting the
16
+ complete result set in memory.
17
+ - `XlsmUpdater` as an explicit alias for `XlsxUpdater`; VBA package members are
18
+ preserved as opaque ZIP parts.
14
19
 
15
20
  ### Fixed
16
21
 
17
22
  - Destination replacement during Excel conversion now preserves files created
18
23
  concurrently and rolls back safely when the final rename fails.
24
+ - Shared-string `count` values now reflect the replaced worksheet rather than
25
+ accumulating stale references across repeated updates. Pending strings are
26
+ committed only once.
27
+ - Updater saves now install the destination atomically through a same-directory
28
+ temporary file.
19
29
 
20
30
  ## [2.1.0] - 2026-08-07
21
31
 
package/README.md CHANGED
@@ -32,7 +32,7 @@ High-performance TypeScript library for reading and writing Excel files in XLSB
32
32
  - **Multiple Sheets** — Support for multiple worksheets per workbook
33
33
  - **Auto-filter** — Automatic filter headers support
34
34
  - **Excel COM Conversion** — Convert XLSB and XLSX through desktop Microsoft Excel on Windows
35
- - **In-place Updates** — Replace the data of a worksheet inside an existing XLSX or XLSB (e.g. `report.xlsx` / `report.xlsb`) while keeping pivot tables, other sheets and formatting untouched
35
+ - **In-place Updates** — Replace the data of a worksheet inside an existing XLSX, XLSM or XLSB while keeping pivot tables, other sheets and formatting untouched
36
36
 
37
37
  ## Benchmark Results
38
38
 
@@ -213,7 +213,7 @@ Excel file (like a report with pivot tables wired to a data sheet) and only
213
213
  refresh its data:
214
214
 
215
215
  ```typescript
216
- import { XlsxUpdater, XlsbUpdater } from '@justybase/spreadsheet-tasks';
216
+ import { XlsxUpdater, XlsbUpdater, XlsmUpdater } from '@justybase/spreadsheet-tasks';
217
217
 
218
218
  // XLSX
219
219
  const xlsx = new XlsxUpdater('report.xlsx');
@@ -224,8 +224,26 @@ xlsx.save(); // overwrite in place, or xlsx.save('report_new.xls
224
224
  const xlsb = new XlsbUpdater('report.xlsb');
225
225
  xlsb.replaceSheetData('data1', rows, { headers: ['ID', 'NAME', 'AMOUNT'] });
226
226
  xlsb.save('report_new.xlsb');
227
+
228
+ // XLSM uses the XLSX updater and keeps opaque VBA parts intact.
229
+ const xlsm = new XlsmUpdater('report.xlsm');
230
+ xlsm.replaceSheetData('data1', rows, { headers: ['ID', 'NAME', 'AMOUNT'] });
231
+ xlsm.save('report_new.xlsm');
232
+ ```
233
+
234
+ For a result set that should not be collected into a 2D array, use the additive
235
+ streaming API. It accepts either a synchronous iterable or an async iterable and
236
+ writes the final ZIP through a temporary file before installing it atomically:
237
+
238
+ ```typescript
239
+ const updater = new XlsxUpdater('large-report.xlsx');
240
+ await updater.replaceSheetDataStream('data1', databaseRows(), { headers });
241
+ await updater.saveStreaming('large-report-updated.xlsx');
227
242
  ```
228
243
 
244
+ `save()` remains available for the existing synchronous API. Both save methods
245
+ replace their destination atomically after a successful write.
246
+
229
247
  **What happens under the hood:**
230
248
 
231
249
  - The target sheet's cell data is cleared and replaced with the new rows.
@@ -367,6 +367,8 @@ interface ReplaceSheetDataOptions$1 {
367
367
  */
368
368
  styleFallback?: 'inherit' | 'general';
369
369
  }
370
+ /** A one-pass synchronous or asynchronous source of worksheet rows. */
371
+ type RowSource$1 = Iterable<ReadonlyArray<CellValue>> | AsyncIterable<ReadonlyArray<CellValue>>;
370
372
  /**
371
373
  * Update the data of a worksheet inside an existing XLSX file without rebuilding
372
374
  * the workbook. Everything outside the target sheet's cell data — pivot tables
@@ -383,6 +385,8 @@ interface ReplaceSheetDataOptions$1 {
383
385
  declare class XlsxUpdater {
384
386
  private readonly zip;
385
387
  private readonly sourcePath;
388
+ private temporaryDirectory;
389
+ private readonly stagedParts;
386
390
  private sheetNameToPath;
387
391
  private pivotCacheDefs;
388
392
  private pivotTableDefPaths;
@@ -390,12 +394,14 @@ declare class XlsxUpdater {
390
394
  private sharedStringsValues;
391
395
  private sharedStringsOriginalLength;
392
396
  private sharedStringsCount;
397
+ private sharedStringsPending;
398
+ private sharedStringsDirty;
393
399
  private stringIndexMap;
394
400
  private readonly _oaEpoch;
395
401
  /**
396
402
  * @param path Path to an existing `.xlsx` file.
397
403
  */
398
- constructor(path: string);
404
+ constructor(filePath: string);
399
405
  /** Worksheet names in workbook order. */
400
406
  getSheetNames(): string[];
401
407
  /**
@@ -407,13 +413,36 @@ declare class XlsxUpdater {
407
413
  * @param options See {@link ReplaceSheetDataOptions}.
408
414
  */
409
415
  replaceSheetData(sheetName: string, rows: CellValue[][], options?: ReplaceSheetDataOptions$1): void;
416
+ /**
417
+ * Replace worksheet rows from a one-pass source without materialising the
418
+ * complete result set. Use {@link saveStreaming} to keep the final ZIP
419
+ * write streaming as well.
420
+ */
421
+ replaceSheetDataStream(sheetName: string, rows: RowSource$1, options?: ReplaceSheetDataOptions$1): Promise<void>;
410
422
  /**
411
423
  * Write the updated workbook to disk.
412
424
  * @param outputPath Destination path; defaults to the source file (overwrites in place).
413
425
  */
414
426
  save(outputPath?: string): void;
427
+ /** Save staged streaming replacements without materialising the ZIP. */
428
+ saveStreaming(outputPath?: string): Promise<void>;
415
429
  /** The updated workbook as an in-memory ZIP buffer. */
416
430
  toBuffer(): Buffer;
431
+ /**
432
+ * Release the staging directory and any unsaved staged parts.
433
+ * The updater remains usable; a new staging directory is created lazily
434
+ * on the next streaming replacement.
435
+ */
436
+ dispose(): void;
437
+ /** Support `using` declarations for automatic staging cleanup. */
438
+ [Symbol.dispose](): void;
439
+ private _ensureTemporaryDirectory;
440
+ private _temporaryPath;
441
+ private _removeTemporaryFile;
442
+ private _entryData;
443
+ private _stagePart;
444
+ private _discardStagedPart;
445
+ private _materializeStagedParts;
417
446
  private _loadWorkbookStructure;
418
447
  private _ensureSharedStringsLoaded;
419
448
  private _commitSharedStrings;
@@ -422,7 +451,10 @@ declare class XlsxUpdater {
422
451
  private _buildSheetData;
423
452
  private _valueCell;
424
453
  private _stringCell;
454
+ private static _replaceOrAddXmlAttribute;
455
+ private _sharedStringRefCount;
425
456
  private _patchSheetXml;
457
+ private _patchSheetFile;
426
458
  private _updatePivotCaches;
427
459
  private _addRefreshOnLoadToPivotTables;
428
460
  }
@@ -445,6 +477,8 @@ interface ReplaceSheetDataOptions {
445
477
  */
446
478
  styleFallback?: 'inherit' | 'general';
447
479
  }
480
+ /** A one-pass synchronous or asynchronous source of worksheet rows. */
481
+ type RowSource = Iterable<ReadonlyArray<CellValue>> | AsyncIterable<ReadonlyArray<CellValue>>;
448
482
  /**
449
483
  * Update the data of a worksheet inside an existing XLSB file without rebuilding
450
484
  * the workbook. Everything outside the target sheet's rows — pivot tables and
@@ -460,6 +494,8 @@ interface ReplaceSheetDataOptions {
460
494
  declare class XlsbUpdater {
461
495
  private readonly zip;
462
496
  private readonly sourcePath;
497
+ private temporaryDirectory;
498
+ private readonly stagedParts;
463
499
  private sheetNameToPath;
464
500
  private pivotCacheDefPaths;
465
501
  private sharedStringsBuf;
@@ -468,11 +504,12 @@ declare class XlsbUpdater {
468
504
  private sharedStringsUnique;
469
505
  private sharedStringsEndSst;
470
506
  private stringIndexMap;
507
+ private sharedStringsDirty;
471
508
  private readonly _oaEpoch;
472
509
  /**
473
510
  * @param path Path to an existing `.xlsb` file.
474
511
  */
475
- constructor(path: string);
512
+ constructor(filePath: string);
476
513
  /** Worksheet names in workbook order. */
477
514
  getSheetNames(): string[];
478
515
  /**
@@ -484,13 +521,32 @@ declare class XlsbUpdater {
484
521
  * @param options See {@link ReplaceSheetDataOptions}.
485
522
  */
486
523
  replaceSheetData(sheetName: string, rows: CellValue[][], options?: ReplaceSheetDataOptions): void;
524
+ /** Replace worksheet rows from a one-pass source. */
525
+ replaceSheetDataStream(sheetName: string, rows: RowSource, options?: ReplaceSheetDataOptions): Promise<void>;
487
526
  /**
488
527
  * Write the updated workbook to disk.
489
528
  * @param outputPath Destination path; defaults to the source file (overwrites in place).
490
529
  */
491
530
  save(outputPath?: string): void;
531
+ /** Save staged streaming replacements without materialising the ZIP. */
532
+ saveStreaming(outputPath?: string): Promise<void>;
492
533
  /** The updated workbook as an in-memory ZIP buffer. */
493
534
  toBuffer(): Buffer;
535
+ /**
536
+ * Release the staging directory and any unsaved staged parts.
537
+ * The updater remains usable; a new staging directory is created lazily
538
+ * on the next streaming replacement.
539
+ */
540
+ dispose(): void;
541
+ /** Support `using` declarations for automatic staging cleanup. */
542
+ [Symbol.dispose](): void;
543
+ private _ensureTemporaryDirectory;
544
+ private _temporaryPath;
545
+ private _removeTemporaryFile;
546
+ private _entryData;
547
+ private _stagePart;
548
+ private _discardStagedPart;
549
+ private _materializeStagedParts;
494
550
  private _loadWorkbookStructure;
495
551
  private _appendCount;
496
552
  private _ensureSharedStringsLoaded;
@@ -500,6 +556,8 @@ declare class XlsbUpdater {
500
556
  private _buildRows;
501
557
  private _valueCellBytes;
502
558
  private _stringCellBytes;
559
+ private _sharedStringRefCount;
560
+ private _patchSheetMetadataFile;
503
561
  private _findRowsRegion;
504
562
  /**
505
563
  * Keep the sheet dimension record (0x98) in sync with the new data.
@@ -566,4 +624,4 @@ declare class StreamingSheetState {
566
624
  end(): void;
567
625
  }
568
626
 
569
- export { BiffReaderWriter, BigBuffer, type CellValue, type ExcelConversionOptions, ExcelReaderAbstract, F, type FormattedCell, INVALID_SHEET_NAME_CHARS, type PrimitiveCellValue, ReaderFactory, type ReplaceSheetDataOptions$1 as ReplaceSheetDataOptions, StreamingSheetState, XlsbReader, type ReplaceSheetDataOptions as XlsbReplaceSheetDataOptions, XlsbUpdater, XlsbWriter, XlsxReader, XlsxUpdater, XlsxWriter, applyHeaderWidths, convertXlsbToXlsx, convertXlsxToXlsb, defaultColWidth, getFormat, initColWidths, isFormattedCell, sanitizeSheetName, unwrapCell, updateColWidthsFromRows };
627
+ export { BiffReaderWriter, BigBuffer, type CellValue, type ExcelConversionOptions, ExcelReaderAbstract, F, type FormattedCell, INVALID_SHEET_NAME_CHARS, type PrimitiveCellValue, ReaderFactory, type ReplaceSheetDataOptions$1 as ReplaceSheetDataOptions, type RowSource$1 as RowSource, StreamingSheetState, XlsbReader, type ReplaceSheetDataOptions as XlsbReplaceSheetDataOptions, XlsbUpdater, XlsbWriter, XlsxUpdater as XlsmUpdater, XlsxReader, XlsxUpdater, XlsxWriter, applyHeaderWidths, convertXlsbToXlsx, convertXlsxToXlsb, defaultColWidth, getFormat, initColWidths, isFormattedCell, sanitizeSheetName, unwrapCell, updateColWidthsFromRows };