@justybase/spreadsheet-tasks 2.1.1 → 3.0.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/docs/API.md CHANGED
@@ -7,6 +7,7 @@
7
7
  - [XlsbReader](#xlsbreader)
8
8
  - [XlsxReader](#xlsxreader)
9
9
  - [XlsxUpdater](#xlsxupdater)
10
+ - [XlsmUpdater](#xlsmupdater)
10
11
  - [XlsbUpdater](#xlsbupdater)
11
12
  - [Excel conversion](#excel-conversion)
12
13
  - [ReaderFactory](#readerfactory)
@@ -61,7 +62,10 @@ Writes data to the current sheet.
61
62
  - `string` - Written as shared string
62
63
  - `number` - Written as number (integer or double)
63
64
  - `boolean` - Written as boolean
64
- - `Date` - Written as Excel date serial number
65
+ - `Date` - Written as Excel date serial number. The serial is taken from the UTC
66
+ time (`getTime()`), not local wall-clock time: `new Date(2024, 5, 30)` in UTC+2
67
+ is stored as 2024-06-29 22:00 and shows that way in Excel. Use `Date.UTC(...)`
68
+ for date-only values
65
69
  - `null` / `undefined` - Written as empty cell
66
70
 
67
71
  **Example:**
@@ -160,6 +164,12 @@ console.log(sheets); // ['Sheet1', 'Sheet2']
160
164
  Reads the next row. Returns `true` if a row was read, `false` if end of sheet reached.
161
165
  Always use `await` (both XLSB and XLSX are async in v2+).
162
166
 
167
+ Empty rows: gaps in the sheet (missing `<row>` / `BrtRowHdr`, including leading
168
+ gaps) are returned as empty rows (`[]`, `getValue()` → `null`). Like xlspy,
169
+ gap positions are yielded; unlike xlspy's padded `[None] * cols`, this reader
170
+ yields a zero-length row so callers must not assume `fieldCount` width.
171
+ The 1904 date system (`workbookPr date1904` / `BrtWbProp`) is honoured.
172
+
163
173
  **Example:**
164
174
  ```typescript
165
175
  while (await reader.read()) {
@@ -167,6 +177,27 @@ while (await reader.read()) {
167
177
  }
168
178
  ```
169
179
 
180
+ #### `selectSheet(nameOrIndex: string | number): Promise<void>`
181
+
182
+ Selects a worksheet for subsequent `read()` calls. Resets row/EOF and `fieldCount`.
183
+ Default is sheet 0.
184
+
185
+ ```typescript
186
+ await reader.selectSheet('Second');
187
+ await reader.selectSheet(0);
188
+ ```
189
+
190
+ #### `nextResult(): Promise<boolean>`
191
+
192
+ Advances to the next worksheet. Returns `true` if another sheet was selected,
193
+ `false` at the end.
194
+
195
+ ```typescript
196
+ do {
197
+ while (await reader.read()) { /* rows */ }
198
+ } while (await reader.nextResult());
199
+ ```
200
+
170
201
  #### `getValue(columnIndex: number): CellValue`
171
202
 
172
203
  Gets value at the specified column index in the current row.
@@ -176,6 +207,9 @@ Gets value at the specified column index in the current row.
176
207
 
177
208
  **Returns:** `CellValue` (string, number, boolean, Date, bigint, or null/undefined)
178
209
 
210
+ Date cells are returned as `Date` objects built from the serial in UTC, the same
211
+ convention the writers use, so a write/read round trip keeps the same instant.
212
+
179
213
  **Example:**
180
214
  ```typescript
181
215
  while (await reader.read()) {
@@ -220,6 +254,16 @@ Returns array of worksheet names.
220
254
  #### `read(): Promise<boolean>`
221
255
 
222
256
  Reads the next row. Returns `true` if a row was read. Always use `await`.
257
+ Empty gap rows (including leading) are returned as empty rows; see
258
+ [XlsbReader](#xlsbreader) for `selectSheet`/`nextResult`, which behave identically.
259
+
260
+ #### `selectSheet(nameOrIndex: string | number): Promise<void>`
261
+
262
+ See [XlsbReader](#xlsbreader).
263
+
264
+ #### `nextResult(): Promise<boolean>`
265
+
266
+ See [XlsbReader](#xlsbreader).
223
267
 
224
268
  #### `getValue(columnIndex: number): CellValue`
225
269
 
@@ -244,8 +288,9 @@ new XlsxUpdater(filePath: string)
244
288
  **Parameters:**
245
289
  - `filePath` - Path to an existing `.xlsx` file
246
290
 
247
- Throws if the file does not exist, is not a valid XLSX, or is an XLSB
248
- (XLSB updates are not supported yet).
291
+ Throws if the file does not exist, is not a valid XLSX package, or is an XLSB.
292
+ Macro-enabled `.xlsm` packages can use the explicit [XlsmUpdater](#xlsmupdater)
293
+ alias; unknown VBA members are preserved.
249
294
 
250
295
  **Example:**
251
296
  ```typescript
@@ -286,6 +331,12 @@ Clears the entire cell data of the target sheet and writes new rows in its place
286
331
  - Pivot tables whose `worksheetSource` references the updated sheet get their
287
332
  `ref` and `recordCount` adjusted, and `refreshOnLoad="1"` is added to the
288
333
  pivot table definitions so Excel refreshes them on open.
334
+ - The stale calculation chain (`xl/calcChain.xml`) is removed with its
335
+ `Content_Types`/rels entries; Excel rebuilds it on open.
336
+ - ListObject tables (`xl/tables/*.xml`) anchored at `A1` get their `<table>`
337
+ and `<autoFilter>` `ref` updated to the new dimension. Tables whose column
338
+ count changes are left untouched (their `tableColumns` would need rebuilding),
339
+ and ranges are capped so they never overlap another table.
289
340
 
290
341
  **Example:**
291
342
  ```typescript
@@ -299,10 +350,28 @@ updater.save('report_new.xlsx');
299
350
  Writes the updated workbook to disk. When `outputPath` is omitted, the source
300
351
  file is overwritten in place.
301
352
 
353
+ #### `replaceSheetDataStream(sheetName: string, rows: RowSource, options?: ReplaceSheetDataOptions): Promise<void>`
354
+
355
+ Replaces worksheet rows from a synchronous or asynchronous one-pass row source.
356
+ The row source is processed incrementally and is not collected into a complete
357
+ 2D array. Use `saveStreaming()` to also avoid materialising the final ZIP buffer.
358
+
359
+ #### `saveStreaming(outputPath?: string): Promise<void>`
360
+
361
+ Writes the updated package through a temporary file and installs it atomically.
362
+ Staged worksheet parts are streamed; `save()` remains the simpler synchronous
363
+ buffering path.
364
+
302
365
  #### `toBuffer(): Buffer`
303
366
 
304
367
  Returns the updated workbook as an in-memory ZIP buffer.
305
368
 
369
+ ## XlsmUpdater
370
+
371
+ `XlsmUpdater` is an explicit alias of `XlsxUpdater` for macro-enabled `.xlsm`
372
+ packages. It has the same API as `XlsxUpdater` and preserves VBA parts without
373
+ parsing or modifying them.
374
+
306
375
  ---
307
376
 
308
377
  ## XlsbUpdater
@@ -364,6 +433,10 @@ Clears the entire cell data of the target sheet and writes new rows in its place
364
433
  - Pivot caches whose `worksheetSource` references the updated sheet get their
365
434
  range and record count adjusted, and the `refreshOnLoad` flag is set so Excel
366
435
  refreshes them on open.
436
+ - The stale calculation chain (`xl/calcChain.bin`) is removed with its
437
+ `Content_Types`/rels entries.
438
+ - XLSB ListObject tables are **not** patched (documented limitation); only the
439
+ sheet dimension (`BrtWsDim`) and pivot caches are updated.
367
440
 
368
441
  **Example:**
369
442
  ```typescript
@@ -458,6 +531,18 @@ await reader.open('data.xlsb');
458
531
 
459
532
  ## Types
460
533
 
534
+ ### Excel limits (throw, xlspy parity)
535
+
536
+ ```typescript
537
+ import { MAX_ROWS, MAX_COLS, MAX_CELL_CHARS } from '@justybase/spreadsheet-tasks';
538
+ // MAX_ROWS = 1_048_576, MAX_COLS = 16_384, MAX_CELL_CHARS = 32_767 UTF-16 units
539
+ ```
540
+
541
+ Writers throw when a limit is exceeded (no truncation, workbook not published).
542
+ `writeSheet` also throws when a row/header is longer than `columnCount`, matching
543
+ streaming `writeRow` behaviour. Astral characters count as 2 units
544
+ (`'😀'.length === 2`).
545
+
461
546
  ### Supported Cell Types
462
547
 
463
548
  | TypeScript Type | Excel Cell Type |
@@ -469,6 +554,16 @@ await reader.open('data.xlsb');
469
554
  | `Date` | Date Serial Number |
470
555
  | `null` / `undefined` | Empty Cell |
471
556
 
557
+ ### `RowSource`
558
+
559
+ ```typescript
560
+ type RowSource =
561
+ | Iterable<ReadonlyArray<CellValue>>
562
+ | AsyncIterable<ReadonlyArray<CellValue>>;
563
+ ```
564
+
565
+ Used by `replaceSheetDataStream()` in both updater classes.
566
+
472
567
  ### Sheet Name Restrictions
473
568
 
474
569
  Sheet names have the following restrictions:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@justybase/spreadsheet-tasks",
3
- "version": "2.1.1",
3
+ "version": "3.0.0",
4
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",
@@ -26,10 +26,10 @@
26
26
  "build": "tsup && node -e \"require('fs').copyFileSync('dist-esm-package.json','dist/esm/package.json')\"",
27
27
  "build:browser": "esbuild src/browser/download.ts --bundle --minify --format=iife --global-name=JustybaseSpreadsheet --outfile=browser/justybase-spreadsheet.min.js && esbuild src/browser/download.ts --bundle --format=esm --outfile=browser/browser-spreadsheet.js",
28
28
  "lint": "eslint src/",
29
- "typecheck": "tsc --noEmit",
29
+ "typecheck": "tsc --noEmit && tsc -p tsconfig.browser.json",
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 && npx ts-node tests/unit/excel-converter.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 && npx ts-node tests/unit/updater-streaming.test.ts && npx ts-node tests/unit/xlsx-reader-cell-refs.test.ts && npx ts-node tests/unit/writer-limits-columns.test.ts && npx ts-node tests/unit/reader-xml-fixes.test.ts && npx ts-node tests/unit/xlsb-sst-dim.test.ts && npx ts-node tests/unit/updater-cleanup.test.ts && npx ts-node tests/unit/oa-1900-leap.test.ts && npx ts-node tests/unit/reader-row-limit.test.ts",
33
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
35
  "test:excel-conversion": "npm run build && npx ts-node tests/excel-compat/conversion.ts",
@@ -38,6 +38,7 @@
38
38
  "benchmark": "npm run build && npx ts-node benchmarks/performance.bench.ts",
39
39
  "benchmark:comparison": "npm run build && npx ts-node benchmarks/comparison.bench.ts",
40
40
  "test:excel-compat": "npm run build && npx tsx tests/excel-compat/run.ts",
41
+ "test:cross": "npx ts-node tests/cross/xlspy-cross.test.ts",
41
42
  "prepublishOnly": "npm run build && npm run build:browser && npm test"
42
43
  },
43
44
  "keywords": [