@justybase/spreadsheet-tasks 2.0.0 → 2.1.1

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/docs/API.md CHANGED
@@ -6,6 +6,9 @@
6
6
  - [XlsxWriter](#xlsxwriter)
7
7
  - [XlsbReader](#xlsbreader)
8
8
  - [XlsxReader](#xlsxreader)
9
+ - [XlsxUpdater](#xlsxupdater)
10
+ - [XlsbUpdater](#xlsbupdater)
11
+ - [Excel conversion](#excel-conversion)
9
12
  - [ReaderFactory](#readerfactory)
10
13
  - [Types](#types)
11
14
 
@@ -224,6 +227,212 @@ Gets value at the specified column index.
224
227
 
225
228
  ---
226
229
 
230
+ ## XlsxUpdater
231
+
232
+ Updates the data of a worksheet inside an existing `.xlsx` file without rebuilding
233
+ the workbook. Everything outside the target sheet's cell data — pivot tables and
234
+ their caches, other sheets, styles, themes, defined names — is preserved, which
235
+ makes it possible to refresh a data sheet that pivot tables / charts / formulas
236
+ are wired to.
237
+
238
+ ### Constructor
239
+
240
+ ```typescript
241
+ new XlsxUpdater(filePath: string)
242
+ ```
243
+
244
+ **Parameters:**
245
+ - `filePath` - Path to an existing `.xlsx` file
246
+
247
+ Throws if the file does not exist, is not a valid XLSX, or is an XLSB
248
+ (XLSB updates are not supported yet).
249
+
250
+ **Example:**
251
+ ```typescript
252
+ const updater = new XlsxUpdater('report.xlsx');
253
+ ```
254
+
255
+ ### Methods
256
+
257
+ #### `getSheetNames(): string[]`
258
+
259
+ Returns the worksheet names in workbook order.
260
+
261
+ #### `replaceSheetData(sheetName: string, rows: CellValue[][], options?: ReplaceSheetDataOptions): void`
262
+
263
+ Clears the entire cell data of the target sheet and writes new rows in its place.
264
+
265
+ **Parameters:**
266
+ - `sheetName` - Name of the worksheet to replace. Throws if not found.
267
+ - `rows` - 2D array of new data (`null` / `undefined` cells stay empty).
268
+ Trailing rows that contain no non-empty cells are trimmed so the written
269
+ range matches the actual data (pivot tables won't pick up "(blank)" items
270
+ from padding rows).
271
+ - `options` - Optional object:
272
+ - `headers?: string[]` - Optional header row written into row 1
273
+ - `styleFallback?: 'inherit' | 'general'` - Style strategy for new cells
274
+ (default `'inherit'`)
275
+
276
+ **Behavior:**
277
+ - The sheet's `<dimension>` and `<autoFilter>` ranges are recalculated and end
278
+ exactly at the last data row.
279
+ - When `headers` are omitted, the first data row lands in row 1 and becomes the
280
+ pivot's field names — always pass `headers` matching the original columns.
281
+ - New strings are appended to `xl/sharedStrings.xml` (existing entries and their
282
+ indices are never modified); files without a shared string table use inline strings.
283
+ - With `styleFallback: 'inherit'` (default), new cells reuse the dominant style
284
+ of their column from the existing data, and `Date` cells fall back to a date
285
+ style found in `styles.xml` when the column has none.
286
+ - Pivot tables whose `worksheetSource` references the updated sheet get their
287
+ `ref` and `recordCount` adjusted, and `refreshOnLoad="1"` is added to the
288
+ pivot table definitions so Excel refreshes them on open.
289
+
290
+ **Example:**
291
+ ```typescript
292
+ const updater = new XlsxUpdater('report.xlsx');
293
+ updater.replaceSheetData('data1', rows, { headers: ['ID', 'NAME'] });
294
+ updater.save('report_new.xlsx');
295
+ ```
296
+
297
+ #### `save(outputPath?: string): void`
298
+
299
+ Writes the updated workbook to disk. When `outputPath` is omitted, the source
300
+ file is overwritten in place.
301
+
302
+ #### `toBuffer(): Buffer`
303
+
304
+ Returns the updated workbook as an in-memory ZIP buffer.
305
+
306
+ ---
307
+
308
+ ## XlsbUpdater
309
+
310
+ Updates the data of a worksheet inside an existing `.xlsb` file without
311
+ rebuilding the workbook. Everything outside the target sheet's rows — pivot
312
+ tables and their caches, other sheets, styles, column widths, views — is
313
+ preserved byte-for-byte.
314
+
315
+ The API is identical to [XlsxUpdater](#xlsxupdater); see that section for the
316
+ behavior of `replaceSheetData` (inherited column styles, shared-strings
317
+ appending, pivot cache range + `refreshOnLoad`).
318
+
319
+ ### Constructor
320
+
321
+ ```typescript
322
+ new XlsbUpdater(filePath: string)
323
+ ```
324
+
325
+ **Parameters:**
326
+ - `filePath` - Path to an existing `.xlsb` file
327
+
328
+ Throws if the file does not exist, is not a valid XLSB, or is an XLSX
329
+ (XLSB-only updater).
330
+
331
+ ### Methods
332
+
333
+ #### `getSheetNames(): string[]`
334
+
335
+ Returns the worksheet names in workbook order.
336
+
337
+ #### `replaceSheetData(sheetName: string, rows: CellValue[][], options?: ReplaceSheetDataOptions): void`
338
+
339
+ Clears the entire cell data of the target sheet and writes new rows in its place.
340
+
341
+ **Parameters:**
342
+ - `sheetName` - Name of the worksheet to replace. Throws if not found.
343
+ - `rows` - 2D array of new data (`null` / `undefined` cells stay empty).
344
+ Trailing rows that contain no non-empty cells are trimmed so the written
345
+ range matches the actual data (pivot tables won't pick up "(blank)" items
346
+ from padding rows).
347
+ - `options` - Optional object:
348
+ - `headers?: string[]` - Optional header row written into row 1
349
+ - `styleFallback?: 'inherit' | 'general'` - Style strategy for new cells
350
+ (default `'inherit'`)
351
+
352
+ **Behavior:**
353
+ - Only the worksheet's row records are replaced; column widths, views, and all
354
+ other sheet records are preserved. Auto-filter ranges are recalculated.
355
+ - The sheet's dimension record and the pivot cache source range end exactly at
356
+ the last data row.
357
+ - When `headers` are omitted, the first data row lands in row 1 and becomes the
358
+ pivot's field names — always pass `headers` matching the original columns.
359
+ - New strings are appended to `xl/sharedStrings.bin` (existing entries and their
360
+ indices are never modified).
361
+ - With `styleFallback: 'inherit'` (default), new cells reuse the dominant cell
362
+ style (xf) of their column from the existing data, and `Date` cells fall back
363
+ to a date style found in `styles.bin` when the column has none.
364
+ - Pivot caches whose `worksheetSource` references the updated sheet get their
365
+ range and record count adjusted, and the `refreshOnLoad` flag is set so Excel
366
+ refreshes them on open.
367
+
368
+ **Example:**
369
+ ```typescript
370
+ const updater = new XlsbUpdater('report.xlsb');
371
+ updater.replaceSheetData('data1', rows, { headers: ['ID', 'NAME'] });
372
+ updater.save('report_new.xlsb');
373
+ ```
374
+
375
+ #### `save(outputPath?: string): void`
376
+
377
+ Writes the updated workbook to disk. When `outputPath` is omitted, the source
378
+ file is overwritten in place.
379
+
380
+ #### `toBuffer(): Buffer`
381
+
382
+ Returns the updated workbook as an in-memory ZIP buffer.
383
+
384
+ ---
385
+
386
+ ## Excel conversion
387
+
388
+ Excel conversion uses the installed desktop Microsoft Excel through COM. It is
389
+ available in the Node.js build on Windows only; the browser build does not
390
+ expose these functions.
391
+
392
+ ### `ExcelConversionOptions`
393
+
394
+ ```typescript
395
+ interface ExcelConversionOptions {
396
+ overwrite?: boolean;
397
+ }
398
+ ```
399
+
400
+ `overwrite` defaults to `false`. Existing destination files are rejected unless
401
+ it is explicitly set to `true`.
402
+
403
+ ### `convertXlsbToXlsx`
404
+
405
+ ```typescript
406
+ convertXlsbToXlsx(
407
+ inputPath: string,
408
+ outputPath: string,
409
+ options?: ExcelConversionOptions
410
+ ): Promise<void>
411
+ ```
412
+
413
+ Converts an existing `.xlsb` file to `.xlsx` using Excel `SaveAs` format 51.
414
+
415
+ ### `convertXlsxToXlsb`
416
+
417
+ ```typescript
418
+ convertXlsxToXlsb(
419
+ inputPath: string,
420
+ outputPath: string,
421
+ options?: ExcelConversionOptions
422
+ ): Promise<void>
423
+ ```
424
+
425
+ Converts an existing `.xlsx` file to `.xlsb` using Excel `SaveAs` format 50.
426
+
427
+ Both functions reject invalid extensions, missing files, protected
428
+ destinations, unavailable COM automation, and Excel errors. The source is
429
+ opened read-only and the destination is first written to a temporary file.
430
+
431
+ ```typescript
432
+ await convertXlsbToXlsx('report.xlsb', 'report.xlsx');
433
+ await convertXlsxToXlsb('report.xlsx', 'report.xlsb', { overwrite: true });
434
+ ```
435
+
227
436
  ## ReaderFactory
228
437
 
229
438
  Factory class for creating appropriate reader based on file extension.
@@ -7,7 +7,7 @@
7
7
  * Run with: npx ts-node examples/basic-read.ts
8
8
  */
9
9
 
10
- import { XlsbReader, XlsxReader, ReaderFactory } from '../dist';
10
+ import { XlsbReader, XlsxReader, ReaderFactory } from '../dist/cjs';
11
11
  import * as path from 'path';
12
12
 
13
13
  async function readXlsbExample() {
@@ -7,7 +7,7 @@
7
7
  * Run with: npx ts-node examples/basic-write.ts
8
8
  */
9
9
 
10
- import { XlsbWriter, XlsxWriter } from '../dist';
10
+ import { XlsbWriter, XlsxWriter } from '../dist/cjs';
11
11
  import * as path from 'path';
12
12
  import * as fs from 'fs';
13
13
 
@@ -8,7 +8,7 @@
8
8
  * Run with: npx ts-node examples/datetime-widths.ts
9
9
  */
10
10
 
11
- import { XlsbWriter, XlsxWriter } from '../dist';
11
+ import { XlsbWriter, XlsxWriter } from '../dist/cjs';
12
12
  import * as path from 'path';
13
13
  import * as fs from 'fs';
14
14
 
@@ -7,7 +7,7 @@
7
7
  * Run with: npx ts-node examples/large-dataset.ts
8
8
  */
9
9
 
10
- import { XlsbWriter, XlsxWriter, XlsbReader, XlsxReader } from '../dist';
10
+ import { XlsbWriter, XlsxWriter, XlsbReader, XlsxReader } from '../dist/cjs';
11
11
  import * as path from 'path';
12
12
  import * as fs from 'fs';
13
13
 
@@ -7,7 +7,7 @@
7
7
  * Run with: npx ts-node examples/multiple-sheets.ts
8
8
  */
9
9
 
10
- import { XlsbWriter, XlsxWriter } from '../dist';
10
+ import { XlsbWriter, XlsxWriter } from '../dist/cjs';
11
11
  import * as path from 'path';
12
12
  import * as fs from 'fs';
13
13
 
@@ -7,13 +7,15 @@
7
7
  * Run with: npx ts-node examples/streaming-example.ts
8
8
  */
9
9
 
10
- import { XlsbWriter } from '../dist/XlsbWriter';
10
+ import { XlsbWriter } from '../dist/cjs';
11
11
  import * as path from 'path';
12
+ import * as fs from 'fs';
12
13
 
13
14
  async function streamingExample() {
14
15
  console.log('=== XlsbWriter Streaming Example ===\n');
15
16
 
16
17
  const outputPath = path.join(__dirname, '..', 'output', 'streaming-example.xlsb');
18
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
17
19
  console.log(`Output file: ${outputPath}\n`);
18
20
 
19
21
  const writer = new XlsbWriter(outputPath);
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Update Existing Workbook Example
3
+ *
4
+ * This example demonstrates how to replace the data of a worksheet inside an
5
+ * existing Excel file (e.g. a report whose pivot tables are wired to a "data1"
6
+ * sheet) without rebuilding the workbook. The same API works for XLSX and XLSB.
7
+ *
8
+ * Run with: npx ts-node examples/update-existing.ts
9
+ */
10
+
11
+ import { XlsxUpdater, XlsbUpdater } from '../dist/cjs';
12
+ import * as path from 'path';
13
+ import * as fs from 'fs';
14
+
15
+ const outputDir = path.join(__dirname, 'output');
16
+ if (!fs.existsSync(outputDir)) {
17
+ fs.mkdirSync(outputDir, { recursive: true });
18
+ }
19
+
20
+ async function updateExistingWorkbook() {
21
+ // New data for the "data1" sheet (same columns as before)
22
+ const rows = [
23
+ [new Date('2026-01-01'), 'PL', 'Warsaw', 1200.5, true],
24
+ [new Date('2026-01-02'), 'DE', 'Berlin', 980.25, true],
25
+ [new Date('2026-01-03'), 'PL', 'Krakow', 1530.0, false],
26
+ ];
27
+ const headers = ['DATEKEY', 'COUNTRY', 'CITY', 'AMOUNT', 'ACTIVE'];
28
+
29
+ for (const ext of ['xlsx', 'xlsb']) {
30
+ const input = path.join(__dirname, '..', `report.${ext}`);
31
+ const output = path.join(outputDir, `report_updated.${ext}`);
32
+
33
+ if (!fs.existsSync(input)) {
34
+ console.log(`Template workbook not found: ${input} (skipping)`);
35
+ continue;
36
+ }
37
+
38
+ // Open the existing workbook (everything outside the replaced sheet
39
+ // — pivot tables, other sheets, styles — is preserved)
40
+ const updater = ext === 'xlsb' ? new XlsbUpdater(input) : new XlsxUpdater(input);
41
+ console.log(`[${ext}] Sheets:`, updater.getSheetNames());
42
+
43
+ updater.replaceSheetData('data1', rows, { headers });
44
+
45
+ // Write the updated workbook to a new file (or omit the path to
46
+ // overwrite the source in place)
47
+ updater.save(output);
48
+ console.log(`[${ext}] Workbook updated: ${output}`);
49
+ }
50
+ }
51
+
52
+ updateExistingWorkbook();
53
+
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@justybase/spreadsheet-tasks",
3
- "version": "2.0.0",
4
- "description": "High-performance TypeScript library for reading and writing Excel files in XLSB and XLSX formats. XLSB is 3.3x faster to write and 2.1x faster to read than XLSX.",
3
+ "version": "2.1.1",
4
+ "description": "High-performance TypeScript library for reading and writing Excel files in XLSB and XLSX formats. XLSB is 3.3x faster to write and 2.3x faster to read than XLSX.",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "module": "./dist/esm/index.js",
7
7
  "types": "./dist/cjs/index.d.ts",
@@ -29,10 +29,11 @@
29
29
  "typecheck": "tsc --noEmit",
30
30
  "test": "npm run typecheck && npm run test:smoke && npm run test:unit && npm run test:integration && npm run test:dist",
31
31
  "test:smoke": "npx ts-node tests/smoke.test.ts",
32
- "test:unit": "npx ts-node tests/unit/bigbuffer.test.ts && npx ts-node tests/unit/biff-reader-writer.test.ts && npx ts-node tests/unit/xlsb-writer.test.ts",
33
- "test:integration": "npx ts-node tests/integration/xlsb-writer.test.ts && npx ts-node tests/integration/xlsx-writer.test.ts",
32
+ "test:unit": "npx ts-node tests/unit/bigbuffer.test.ts && npx ts-node tests/unit/biff-reader-writer.test.ts && npx ts-node tests/unit/xlsb-writer.test.ts && npx ts-node tests/unit/excel-converter.test.ts",
33
+ "test:integration": "npx ts-node tests/integration/xlsb-writer.test.ts && npx ts-node tests/integration/xlsx-writer.test.ts && npx ts-node tests/integration/xlsx-updater.test.ts && npx ts-node tests/integration/xlsb-updater.test.ts",
34
34
  "test:dist": "npm run build && npx ts-node tests/dist-surface.test.ts",
35
- "test:xlsb": "npx ts-node tests/integration/xlsb-writer.test.ts",
35
+ "test:excel-conversion": "npm run build && npx ts-node tests/excel-compat/conversion.ts",
36
+ "test:xlsb": "npx ts-node tests/integration/xlsb-writer.test.ts && npx ts-node tests/integration/xlsb-updater.test.ts",
36
37
  "test:xlsx": "npx ts-node tests/integration/xlsx-writer.test.ts",
37
38
  "benchmark": "npm run build && npx ts-node benchmarks/performance.bench.ts",
38
39
  "benchmark:comparison": "npm run build && npx ts-node benchmarks/comparison.bench.ts",