@nazarkalytiuk/rusc-xlsx 2.0.0 → 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.
Files changed (6) hide show
  1. package/README.md +68 -5
  2. package/index.d.ts +117 -22
  3. package/index.js +217 -82
  4. package/package.json +44 -17
  5. package/wrapper.d.ts +149 -532
  6. package/wrapper.js +1227 -1423
package/README.md CHANGED
@@ -68,7 +68,7 @@ npm install @nazarkalytiuk/rusc-xlsx
68
68
  ### Requirements
69
69
 
70
70
  - Node.js >= 22.13
71
- - Rust >= 1.88 (only when building from source)
71
+ - Rust >= 1.95 (only when building from source)
72
72
 
73
73
  ## Quick Start
74
74
 
@@ -171,6 +171,9 @@ workbook.save('output.xlsx');
171
171
  - `setTempdir(path)` - Set temp directory for memory modes
172
172
  - `setDefaultFormat(format, rowHeight, columnWidth)` - Set workbook-wide default formatting before adding worksheets
173
173
  - `useExcel2023Theme()` / `useCustomTheme(path)` - Select the workbook theme before adding worksheets
174
+ - `setCalculationMode(mode)` - Formula recalculation: `'automatic'`, `'manual'` or `'partial'`
175
+ - `setZipCompressionLevel(level)` - Trade file size for speed (1-9, default 6)
176
+ - `setTabRatio(ratio)` - Width of the sheet tab bar vs. the horizontal scroll bar (0-100)
174
177
  - `save(filename)` - Save to file
175
178
  - `saveToBuffer()` - Save to Buffer
176
179
  - `saveToStream(writable)` - Save through a temp file and await stream completion
@@ -187,7 +190,7 @@ workbook.save('output.xlsx');
187
190
  - **Features**: `insertNote()`, `addDataValidation()`, `insertChart()`, `insertImage()`, `insertImageFitToCellCentered()`, `addTable()`, `insertButton()`, `insertShape()`
188
191
  - **Conditional Formatting**: 14 methods for all rule types
189
192
  - **Page Setup**: 26 methods for printing, margins, headers, etc.
190
- - **View**: `autofilter()`, `setFreezePanes()`, `setZoom()`, `setTabColor()`, `hideSheet()`
193
+ - **View**: `autofilter()`, `setFreezePanes()`, `setZoom()`, `setTabColor()`, `setHiddenZeroes()`, `setHidden()`
191
194
  - **Hyperlinks**: `writeUrl()`
192
195
  - **Protection**: `protect()`, `protectWithPassword()`
193
196
  - **Rich Text**: `writeRichString()`
@@ -247,14 +250,31 @@ See the `examples/` directory for complete examples:
247
250
  git clone https://github.com/NazarKalytiuk/napi_rust_xlsxwrighter.git
248
251
  cd napi_rust_xlsxwrighter
249
252
 
250
- # Install dependencies
253
+ # Install dependencies (also builds wrapper.js + wrapper.d.ts from wrapper.ts via tsdown)
251
254
  npm install
252
255
 
253
256
  # Build native module
254
257
  npm run build
255
258
 
256
- # Run tests (577 tests)
259
+ # Rebuild the JavaScript wrapper after editing wrapper.ts
260
+ npm run build:wrapper
261
+
262
+ # Run tests (Vitest)
257
263
  npm test
264
+ npm run test:watch # re-run on change
265
+ npm run test:coverage # V8 coverage report for wrapper.ts
266
+ npm run test:coverage:rust # line coverage of src/*.rs via an instrumented addon;
267
+ # needs cargo-llvm-cov and `rustup component add llvm-tools`
268
+
269
+ # Format, lint (oxfmt, oxlint, rustfmt, clippy) and typecheck — same as CI
270
+ npm run check
271
+ npm run fmt # apply oxfmt + rustfmt
272
+
273
+ # rust_xlsxwriter coverage checklist (rust-xlsxwriter-coverage-checklist.md)
274
+ npm run coverage:check # verify test call sites, counts and the pinned version
275
+ npm run coverage:sync # refresh them after editing tests or row statuses; after a
276
+ # rust_xlsxwriter upgrade it documents the new release with
277
+ # rustdoc JSON (`rustup toolchain install nightly-2026-11-13`)
258
278
 
259
279
  # Run examples
260
280
  node examples/basic.js
@@ -267,7 +287,7 @@ node examples/basic.js
267
287
  Built with Rust and NAPI-RS for native-level performance:
268
288
  - **Fast execution**: Native Rust performance via NAPI bindings
269
289
  - **Low memory usage**: Optimized memory footprint across all modes
270
- - **Test performance**: All 577 tests complete in < 1 second
290
+ - **Test performance**: All 601 tests complete in < 1 second
271
291
 
272
292
  ### Memory Modes
273
293
 
@@ -337,6 +357,49 @@ for await (const item of cursor) {
337
357
  This keeps memory bounded to the current cursor item while retaining
338
358
  constant-memory worksheet generation.
339
359
 
360
+ ### Node.js vs Bun Benchmarks
361
+
362
+ The runtime benchmark executes the same package workload through the same
363
+ release N-API binary on Node.js and Bun:
364
+
365
+ ```bash
366
+ npm run benchmark:runtimes
367
+ ```
368
+
369
+ Bun must be installed and available in `PATH`. The default workload generates
370
+ 200,000 rows × 20 columns through `writeRow()` in constant-memory mode. The
371
+ runner alternates runtime order across three runs, reports both in-process
372
+ generation time and end-to-end process wall time, and verifies identical
373
+ worksheet XML size and CRC for every Node.js/Bun pair.
374
+
375
+ Override the workload or package API, or reuse the existing release build:
376
+
377
+ ```bash
378
+ npm run benchmark:runtimes -- --rows 100000 --columns 20 --runs 5
379
+ npm run benchmark:runtimes -- --write-mode cell
380
+ npm run benchmark:runtimes -- --write-mode batch --batch-rows 1000
381
+ npm run benchmark:runtimes -- --skip-build
382
+ ```
383
+
384
+ #### ExcelJS runtime comparison
385
+
386
+ To isolate the runtime difference for a JavaScript XLSX implementation, run the
387
+ same dataset through the ExcelJS streaming writer:
388
+
389
+ ```bash
390
+ npm run benchmark:runtimes:exceljs
391
+ ```
392
+
393
+ This profile uses ExcelJS `stream.xlsx.WorkbookWriter`, disables styles and
394
+ shared strings, fixes ZIP DEFLATE compression at level 6, and commits every
395
+ row. It uses the same alternating order, timing and memory metrics, and
396
+ worksheet-equivalence checks as the native-package profile. Override the
397
+ default workload when needed:
398
+
399
+ ```bash
400
+ npm run benchmark:runtimes:exceljs -- --rows 100000 --columns 20 --runs 5
401
+ ```
402
+
340
403
  ### Node.js vs Go, C#, and Python Benchmark
341
404
 
342
405
  The cross-language benchmark compares concrete streaming XLSX libraries, not
package/index.d.ts CHANGED
@@ -1,5 +1,14 @@
1
1
  /* auto-generated by NAPI-RS */
2
2
  /* eslint-disable */
3
+
4
+ /**
5
+ * Which binding artifact the generated loader actually loaded: `'native'` for
6
+ * a native addon, otherwise the `platformArchABI` of the WASI flavor. Every
7
+ * flavor napi-rs can build is listed, because `NAPI_RS_NATIVE_LIBRARY_PATH`
8
+ * can point the loader at a WASI artifact this package does not build itself.
9
+ */
10
+ export declare const __napiBindingTarget: 'native' | 'wasm32-wasi' | 'wasm32-wasip1'
11
+
3
12
  export declare class RuscButton {
4
13
  /** Create a new Button */
5
14
  static new(): RuscButton
@@ -178,6 +187,13 @@ export declare class RuscFormat {
178
187
  setQuotePrefix(): void
179
188
  }
180
189
 
190
+ /**
191
+ * A formula for the native formula writers.
192
+ *
193
+ * @deprecated Pass a `RuscFormulaData` object instead. An instance keeps its Rust
194
+ * memory until its finalizer runs after the event loop turns, so instances
195
+ * created in a synchronous loop accumulate.
196
+ */
181
197
  export declare class RuscFormula {
182
198
  /** Create a new formula */
183
199
  constructor(formula: string)
@@ -272,15 +288,21 @@ export declare class RuscProtectionOptions {
272
288
  setContents(allow: boolean): this
273
289
  }
274
290
 
275
- /** Wrapper for rich text strings (multi-format text in a single cell) */
291
+ /**
292
+ * Wrapper for rich text strings (multi-format text in a single cell)
293
+ *
294
+ * @deprecated Pass `[text, format]` segments to `RuscWorksheet.writeRichString`
295
+ * instead. An instance keeps its Rust memory until its finalizer runs after the
296
+ * event loop turns, so instances created in a synchronous loop accumulate.
297
+ */
276
298
  export declare class RuscRichText {
277
299
  /** Create a new empty rich text string */
278
300
  constructor()
279
301
  /**
280
- * Add a text segment with formatting
281
- * The format is cloned internally for storage
302
+ * Add a text segment, optionally formatted; without a format the segment uses
303
+ * the default cell font. The format is cloned internally for storage.
282
304
  */
283
- addSegment(text: string, format: RuscFormat): this
305
+ addSegment(text: string, format?: RuscFormat | undefined | null): this
284
306
  }
285
307
 
286
308
  export declare class RuscShape {
@@ -301,7 +323,10 @@ export declare class RuscShape {
301
323
  export declare class RuscSparkline {
302
324
  /** Create a new Sparkline */
303
325
  static new(): RuscSparkline
304
- /** Set the data range for the sparkline (e.g., "Sheet1!A1:E1") */
326
+ /**
327
+ * Set the data range for the sparkline, e.g. "Sheet1!A1:E1", or "A1:E1"
328
+ * for a range on the worksheet the sparkline is added to
329
+ */
305
330
  setRange(range: string): this
306
331
  /** Set the type of sparkline: 'line', 'column', or 'winLose' */
307
332
  setType(sparklineType: string): this
@@ -374,7 +399,10 @@ export declare class RuscTable {
374
399
  setAltText(altText: string): this
375
400
  /** Set the alternative text title for screen readers. */
376
401
  setAltTextTitle(title: string): this
377
- /** Set the style of the table (1-21 for light styles, 22-49 for medium styles, 50-61 for dark styles) */
402
+ /**
403
+ * Set the style of the table: 0 for no style, 1-21 for light styles,
404
+ * 22-49 for medium styles and 50-60 for dark styles
405
+ */
378
406
  setStyle(styleId: number): this
379
407
  /** Add columns to the table */
380
408
  setColumns(columns: Array<RuscTableColumn>): this
@@ -410,6 +438,21 @@ export declare class RuscWorkbook {
410
438
  * Must be called before adding worksheets.
411
439
  */
412
440
  setDefaultFormat(format: RuscFormat, rowHeight: number, columnWidth: number): void
441
+ /**
442
+ * Set the zip compression level of the xlsx container (1-9, default 6).
443
+ * Lower levels are faster, higher levels produce smaller files.
444
+ */
445
+ setZipCompressionLevel(level: number): void
446
+ /**
447
+ * Set when Excel recalculates formulas: "automatic" (default), "manual",
448
+ * or "partial" (automatic except for data tables).
449
+ */
450
+ setCalculationMode(mode: string): void
451
+ /**
452
+ * Set the width of the worksheet tab bar as a percentage (0-100, default
453
+ * 60) of the space it shares with the horizontal scroll bar.
454
+ */
455
+ setTabRatio(ratio: number): void
413
456
  /**
414
457
  * Use the Excel 2023 Office theme with the Aptos default font.
415
458
  * Must be called before adding worksheets.
@@ -452,20 +495,25 @@ export declare class RuscWorkbook {
452
495
 
453
496
  /** Worksheet wrapper for Excel sheet manipulation */
454
497
  export declare class RuscWorksheet {
455
- /** Write a value to a cell (auto-detects type) */
456
- write(row: number, col: number, value: string | number | boolean, format?: RuscFormat | undefined | null): void
498
+ /**
499
+ * Write a value to a cell (auto-detects type). `null` or `undefined`
500
+ * writes nothing, or a blank cell with the format when one is given.
501
+ */
502
+ write(row: number, col: number, value: string | number | boolean | null | undefined, format?: RuscFormat | undefined | null): void
457
503
  /**
458
504
  * Write an array of values as a row starting at the given column.
459
505
  * Uses lazy Array iteration — reads one cell at a time from the JS array
460
506
  * instead of deserializing the entire row into Rust memory.
461
- * Null/undefined values are skipped (preserving column positions).
507
+ * Values are written as `write` writes them: `null`, `undefined` and
508
+ * holes leave the cell empty, or write a blank cell with the format.
462
509
  */
463
510
  writeRow(row: number, startCol: number, values: unknown[], format?: RuscFormat | undefined | null): void
464
511
  /**
465
512
  * Write a 2D array of values starting at the given position.
466
513
  * Uses lazy Array iteration — reads one row/cell at a time from JS,
467
514
  * never materializing the entire grid in Rust memory.
468
- * Null/undefined values are skipped (preserving column positions).
515
+ * Values are written as `write` writes them: `null`, `undefined` and
516
+ * holes leave the cell empty, or write a blank cell with the format.
469
517
  */
470
518
  writeRows(startRow: number, startCol: number, rows: unknown[], format?: RuscFormat | undefined | null): void
471
519
  /** Write a string to a cell */
@@ -476,10 +524,22 @@ export declare class RuscWorksheet {
476
524
  writeBoolean(row: number, col: number, value: boolean): void
477
525
  /** Write a blank cell with formatting */
478
526
  writeBlank(row: number, col: number, format: RuscFormat): void
479
- /** Write a rich text string (multi-format text) to a cell */
480
- writeRichString(row: number, col: number, richText: RuscRichText): void
481
- /** Write a rich text string with an additional cell format */
482
- writeRichStringWithFormat(row: number, col: number, richText: RuscRichText, cellFormat: RuscFormat): void
527
+ /**
528
+ * Write a rich text string (multi-format text) to a cell.
529
+ *
530
+ * `richText` is `[text, format]` segments, or a deprecated `RuscRichText`; a
531
+ * segment without a format uses the default cell font. Segments are plain data,
532
+ * so nothing outlives the call. Empty segments are skipped, and a rich string
533
+ * whose segments are all empty leaves the cell untouched.
534
+ */
535
+ writeRichString(row: number, col: number, richText: RuscRichText | Array<[string, RuscFormat | undefined | null]>): void
536
+ /**
537
+ * Write a rich text string with an additional cell format.
538
+ *
539
+ * `richText` is as for `writeRichString`. A rich string whose segments are all
540
+ * empty writes a blank cell with the cell format.
541
+ */
542
+ writeRichStringWithFormat(row: number, col: number, richText: RuscRichText | Array<[string, RuscFormat | undefined | null]>, cellFormat: RuscFormat): void
483
543
  /** Write a datetime from a Unix timestamp (seconds since 1970-01-01 UTC) */
484
544
  writeDatetimeFromTimestamp(row: number, col: number, timestamp: number, format?: RuscFormat | undefined | null): void
485
545
  /** Write a datetime from year, month, day components */
@@ -488,8 +548,11 @@ export declare class RuscWorksheet {
488
548
  writeDatetimeFromYmdHms(row: number, col: number, year: number, month: number, day: number, hour: number, min: number, sec: number, format?: RuscFormat | undefined | null): void
489
549
  /** Write a datetime from an ISO 8601 string (e.g., "2023-01-25T12:30:00") */
490
550
  writeDatetimeFromString(row: number, col: number, datetimeStr: string, format?: RuscFormat | undefined | null): void
491
- /** Write a value with formatting to a cell */
492
- writeWithFormat(row: number, col: number, value: string | number | boolean, format: RuscFormat): void
551
+ /**
552
+ * Write a value with formatting to a cell; `null` or `undefined` writes
553
+ * a blank cell with the format
554
+ */
555
+ writeWithFormat(row: number, col: number, value: string | number | boolean | null | undefined, format: RuscFormat): void
493
556
  /** Set the width of a column */
494
557
  setColumnWidth(col: number, width: number): void
495
558
  /** Set the height of a row */
@@ -513,11 +576,11 @@ export declare class RuscWorksheet {
513
576
  /** Set the default author name for all notes in the worksheet */
514
577
  setDefaultNoteAuthor(name: string): void
515
578
  /** Write a formula to a cell */
516
- writeFormula(row: number, col: number, formula: RuscFormula, format?: RuscFormat | undefined | null): void
579
+ writeFormula(row: number, col: number, formula: RuscFormula | RuscFormulaData, format?: RuscFormat | undefined | null): void
517
580
  /** Write a dynamic array formula to a cell (Excel 365) */
518
- writeDynamicFormula(row: number, col: number, formula: RuscFormula, format?: RuscFormat | undefined | null): void
581
+ writeDynamicFormula(row: number, col: number, formula: RuscFormula | RuscFormulaData, format?: RuscFormat | undefined | null): void
519
582
  /** Write an array formula to a range of cells */
520
- writeArrayFormula(firstRow: number, firstCol: number, lastRow: number, lastCol: number, formula: RuscFormula, format?: RuscFormat | undefined | null): void
583
+ writeArrayFormula(firstRow: number, firstCol: number, lastRow: number, lastCol: number, formula: RuscFormula | RuscFormulaData, format?: RuscFormat | undefined | null): void
521
584
  /** Add data validation to a cell or range */
522
585
  addDataValidation(firstRow: number, firstCol: number, lastRow: number, lastCol: number, validation: RuscDataValidation): void
523
586
  /** Write a URL to a cell */
@@ -600,6 +663,11 @@ export declare class RuscWorksheet {
600
663
  setHidden(enable: boolean): void
601
664
  /** Set the worksheet zoom level (10-400) */
602
665
  setZoom(zoom: number): void
666
+ /**
667
+ * Hide zero values in worksheet cells, the equivalent of turning off the
668
+ * Excel option "Show a zero in cells that have zero value"
669
+ */
670
+ setHiddenZeroes(enable: boolean): void
603
671
  /** Set the default cell selection when opening the worksheet */
604
672
  setSelection(firstRow: number, firstCol: number, lastRow: number, lastCol: number): void
605
673
  /** Set which cell appears in the top-left corner when opening the worksheet */
@@ -640,8 +708,35 @@ export declare class RuscWorksheet {
640
708
  insertShapeWithOffset(row: number, col: number, shape: RuscShape, xOffset: number, yOffset: number): void
641
709
  }
642
710
 
643
- /** Resets peak memory tracking to current usage */
711
+ /**
712
+ * The data of a JavaScript `Formula`: the formula text and, for applications
713
+ * that don't recalculate, its cached result.
714
+ *
715
+ * It is plain data rather than a class so that a formula written per row owns no
716
+ * Rust memory between writes.
717
+ */
718
+ export interface RuscFormulaData {
719
+ formula: string
720
+ result?: string
721
+ }
722
+
723
+ /**
724
+ * Resets peak memory tracking to the current allocation level.
725
+ * Call before a section you want to measure.
726
+ *
727
+ * @example
728
+ * rustMemoryResetPeak();
729
+ * // ... do work ...
730
+ * const [current, peak] = rustMemoryStats();
731
+ */
644
732
  export declare function rustMemoryResetPeak(): void
645
733
 
646
- /** Returns [currentBytes, peakBytes] of Rust allocator usage */
647
- export declare function rustMemoryStats(): Array<number>
734
+ /**
735
+ * Returns [currentBytes, peakBytes] of Rust allocator usage.
736
+ * Useful for profiling memory consumption of the native layer.
737
+ *
738
+ * @example
739
+ * const [current, peak] = rustMemoryStats();
740
+ * console.log(`Rust heap: ${(current / 1024 / 1024).toFixed(2)} MB (peak: ${(peak / 1024 / 1024).toFixed(2)} MB)`);
741
+ */
742
+ export declare function rustMemoryStats(): [number, number]