@niicojs/excel 0.2.7 → 0.3.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/README.md CHANGED
@@ -1,352 +1,585 @@
1
- # @niicojs/excel
2
-
3
- A TypeScript library for Excel/OpenXML manipulation with maximum format preservation.
4
-
5
- ## Features
6
-
7
- - Read and write `.xlsx` files
8
- - Preserve formatting when modifying existing files
9
- - Full formula support (read/write/preserve)
10
- - Cell styles (fonts, fills, borders, alignment)
11
- - Merged cells
12
- - Sheet operations (add, delete, rename, copy)
13
- - Create sheets from arrays of objects (`addSheetFromData`)
14
- - Convert sheets to JSON arrays (`toJson`)
15
- - Create new workbooks from scratch
16
- - TypeScript-first with full type definitions
17
-
18
- ## Installation
19
-
20
- ```bash
21
- pnpm install @niicojs/excel
22
- # or
23
- bun add @niicojs/excel
24
- ```
25
-
26
- ## Quick Start
27
-
28
- ```typescript
29
- import { Workbook } from '@niicojs/excel';
30
-
31
- // Create a new workbook
32
- const wb = Workbook.create();
33
- wb.addSheet('Sheet1');
34
- const sheet = wb.sheet('Sheet1');
35
-
36
- // Write data
37
- sheet.cell('A1').value = 'Hello';
38
- sheet.cell('B1').value = 42;
39
- sheet.cell('C1').value = true;
40
- sheet.cell('D1').value = new Date();
41
-
42
- // Write formulas
43
- sheet.cell('A2').formula = 'SUM(B1:B1)';
44
-
45
- // Write a 2D array
46
- sheet.cell('A3').values = [
47
- ['Name', 'Age', 'City'],
48
- ['Alice', 30, 'NYC'],
49
- ['Bob', 25, 'LA'],
50
- ];
51
-
52
- // Save to file
53
- await wb.toFile('output.xlsx');
54
- ```
55
-
56
- ## Loading Existing Files
57
-
58
- ```typescript
59
- import { Workbook } from '@niicojs/excel';
60
-
61
- // Load from file
62
- const wb = await Workbook.fromFile('template.xlsx');
63
-
64
- // Or load from buffer
65
- const buffer = await fetch('https://example.com/file.xlsx').then((r) => r.arrayBuffer());
66
- const wb = await Workbook.fromBuffer(new Uint8Array(buffer));
67
-
68
- // Read data
69
- const sheet = wb.sheet('Sheet1');
70
- console.log(sheet.cell('A1').value); // The cell value
71
- console.log(sheet.cell('A1').formula); // The formula (if any)
72
- console.log(sheet.cell('A1').type); // 'string' | 'number' | 'boolean' | 'date' | 'error' | 'empty'
73
- ```
74
-
75
- ## Working with Ranges
76
-
77
- ```typescript
78
- const sheet = wb.sheet(0);
79
-
80
- // Read a range
81
- const values = sheet.range('A1:C10').values; // 2D array
82
-
83
- // Write to a range
84
- sheet.range('A1:B2').values = [
85
- [1, 2],
86
- [3, 4],
87
- ];
88
-
89
- // Get formulas from a range
90
- const formulas = sheet.range('A1:C10').formulas;
91
- ```
92
-
93
- ## Styling Cells
94
-
95
- ```typescript
96
- const sheet = wb.sheet(0);
97
-
98
- // Apply styles to a cell
99
- sheet.cell('A1').style = {
100
- bold: true,
101
- italic: true,
102
- fontSize: 14,
103
- fontName: 'Arial',
104
- fontColor: '#FF0000',
105
- fill: '#FFFF00',
106
- border: {
107
- top: 'thin',
108
- bottom: 'medium',
109
- left: 'thin',
110
- right: 'thin',
111
- },
112
- alignment: {
113
- horizontal: 'center',
114
- vertical: 'middle',
115
- wrapText: true,
116
- },
117
- numberFormat: '#,##0.00',
118
- };
119
-
120
- // Apply styles to a range
121
- sheet.range('A1:C1').style = { bold: true, fill: '#CCCCCC' };
122
- ```
123
-
124
- ## Merged Cells
125
-
126
- ```typescript
127
- const sheet = wb.sheet(0);
128
-
129
- // Merge cells
130
- sheet.mergeCells('A1:C1');
131
-
132
- // Or using two addresses
133
- sheet.mergeCells('A1', 'C1');
134
-
135
- // Unmerge
136
- sheet.unmergeCells('A1:C1');
137
-
138
- // Get all merged regions
139
- console.log(sheet.mergedCells); // ['A1:C3', 'D5:E10', ...]
140
- ```
141
-
142
- ## Sheet Operations
143
-
144
- ```typescript
145
- const wb = Workbook.create();
146
-
147
- // Add sheets
148
- wb.addSheet('Data');
149
- wb.addSheet('Summary', 0); // Insert at index 0
150
-
151
- // Get sheet names
152
- console.log(wb.sheetNames); // ['Summary', 'Data']
153
-
154
- // Access sheets
155
- const sheet = wb.sheet('Data'); // By name
156
- const sheet = wb.sheet(0); // By index
157
-
158
- // Rename sheet
159
- wb.renameSheet('Data', 'RawData');
160
-
161
- // Copy sheet
162
- wb.copySheet('RawData', 'RawData_Backup');
163
-
164
- // Delete sheet
165
- wb.deleteSheet('Summary');
166
- ```
167
-
168
- ## Creating Sheets from Data
169
-
170
- Create sheets directly from arrays of objects with `addSheetFromData`:
171
-
172
- ```typescript
173
- const wb = Workbook.create();
174
-
175
- // Simple usage - object keys become column headers
176
- const employees = [
177
- { name: 'Alice', age: 30, city: 'Paris' },
178
- { name: 'Bob', age: 25, city: 'London' },
179
- ];
180
-
181
- wb.addSheetFromData({
182
- name: 'Employees',
183
- data: employees,
184
- });
185
-
186
- // Custom column configuration
187
- wb.addSheetFromData({
188
- name: 'Custom',
189
- data: employees,
190
- columns: [
191
- { key: 'name', header: 'Full Name' },
192
- { key: 'age', header: 'Age (years)' },
193
- { key: 'city', header: 'Location', style: { bold: true } },
194
- ],
195
- });
196
-
197
- // With formulas and styles using RichCellValue
198
- const orderLines = [
199
- { product: 'Widget', price: 10, qty: 5, total: { formula: 'B2*C2', style: { bold: true } } },
200
- { product: 'Gadget', price: 20, qty: 3, total: { formula: 'B3*C3', style: { bold: true } } },
201
- ];
202
-
203
- wb.addSheetFromData({
204
- name: 'Orders',
205
- data: orderLines,
206
- });
207
-
208
- // Other options
209
- wb.addSheetFromData({
210
- name: 'Options',
211
- data: employees,
212
- headerStyle: false, // Don't bold headers
213
- startCell: 'B3', // Start at B3 instead of A1
214
- });
215
- ```
216
-
217
- ## Converting Sheets to JSON
218
-
219
- Convert sheet data back to arrays of objects with `toJson`:
220
-
221
- ```typescript
222
- const sheet = wb.sheet('Data');
223
-
224
- // Using first row as headers
225
- const data = sheet.toJson();
226
- // [{ name: 'Alice', age: 30 }, { name: 'Bob', age: 25 }]
227
-
228
- // Using custom field names (first row is data, not headers)
229
- const data = sheet.toJson({
230
- fields: ['name', 'age', 'city'],
231
- });
232
-
233
- // With TypeScript generics
234
- interface Person {
235
- name: string | null;
236
- age: number | null;
237
- }
238
- const people = sheet.toJson<Person>();
239
-
240
- // Starting from a specific position
241
- const data = sheet.toJson({
242
- startRow: 2, // Skip first 2 rows (0-based)
243
- startCol: 1, // Start from column B
244
- });
245
-
246
- // Limiting the range
247
- const data = sheet.toJson({
248
- endRow: 10, // Stop at row 11 (0-based, inclusive)
249
- endCol: 3, // Only read columns A-D
250
- });
251
-
252
- // Continue past empty rows
253
- const data = sheet.toJson({
254
- stopOnEmptyRow: false, // Default is true
255
- });
256
- ```
257
-
258
- ### Roundtrip Example
259
-
260
- ```typescript
261
- // Create from objects
262
- const originalData = [
263
- { name: 'Alice', age: 30 },
264
- { name: 'Bob', age: 25 },
265
- ];
266
-
267
- const sheet = wb.addSheetFromData({
268
- name: 'People',
269
- data: originalData,
270
- });
271
-
272
- // Read back as objects
273
- const readData = sheet.toJson();
274
- // readData equals originalData
275
- ```
276
-
277
- ## Saving
278
-
279
- ```typescript
280
- // Save to file
281
- await wb.toFile('output.xlsx');
282
-
283
- // Save to buffer (Uint8Array)
284
- const buffer = await wb.toBuffer();
285
- ```
286
-
287
- ## Type Definitions
288
-
289
- ```typescript
290
- // Cell values
291
- type CellValue = number | string | boolean | Date | null | CellError;
292
-
293
- interface CellError {
294
- error: '#NULL!' | '#DIV/0!' | '#VALUE!' | '#REF!' | '#NAME?' | '#NUM!' | '#N/A';
295
- }
296
-
297
- // Cell types
298
- type CellType = 'number' | 'string' | 'boolean' | 'date' | 'error' | 'empty';
299
-
300
- // Border types
301
- type BorderType = 'thin' | 'medium' | 'thick' | 'double' | 'dotted' | 'dashed';
302
-
303
- // Configuration for addSheetFromData
304
- interface SheetFromDataConfig<T> {
305
- name: string;
306
- data: T[];
307
- columns?: ColumnConfig<T>[];
308
- headerStyle?: boolean; // Default: true
309
- startCell?: string; // Default: 'A1'
310
- }
311
-
312
- interface ColumnConfig<T> {
313
- key: keyof T;
314
- header?: string;
315
- style?: CellStyle;
316
- }
317
-
318
- // Rich cell value for formulas/styles in data
319
- interface RichCellValue {
320
- value?: CellValue;
321
- formula?: string;
322
- style?: CellStyle;
323
- }
324
-
325
- // Configuration for toJson
326
- interface SheetToJsonConfig {
327
- fields?: string[];
328
- startRow?: number;
329
- startCol?: number;
330
- endRow?: number;
331
- endCol?: number;
332
- stopOnEmptyRow?: boolean; // Default: true
333
- }
334
- ```
335
-
336
- ## Format Preservation
337
-
338
- When modifying existing Excel files, this library preserves:
339
-
340
- - Cell formatting and styles
341
- - Formulas
342
- - Charts and images
343
- - Merged cells
344
- - Conditional formatting
345
- - Data validation
346
- - And other Excel features
347
-
348
- This is achieved by only modifying what's necessary and keeping the original XML structure intact.
349
-
350
- ## License
351
-
352
- MIT
1
+ # @niicojs/excel
2
+
3
+ A TypeScript library for Excel/OpenXML manipulation with maximum format preservation.
4
+
5
+ ## Features
6
+
7
+ - Read and write `.xlsx` files
8
+ - Preserve formatting when modifying existing files
9
+ - Full formula support (read/write/preserve)
10
+ - Cell styles (fonts, fills, borders, alignment)
11
+ - Merged cells
12
+ - Column widths and row heights
13
+ - Freeze panes
14
+ - Pivot tables with fluent API
15
+ - Sheet operations (add, delete, rename, copy)
16
+ - Create sheets from arrays of objects (`addSheetFromData`)
17
+ - Convert sheets to JSON arrays (`toJson`)
18
+ - Create new workbooks from scratch
19
+ - TypeScript-first with full type definitions
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ pnpm install @niicojs/excel
25
+ # or
26
+ bun add @niicojs/excel
27
+ ```
28
+
29
+ ## Quick Start
30
+
31
+ ```typescript
32
+ import { Workbook } from '@niicojs/excel';
33
+
34
+ // Create a new workbook
35
+ const wb = Workbook.create();
36
+ wb.addSheet('Sheet1');
37
+ const sheet = wb.sheet('Sheet1');
38
+
39
+ // Write data
40
+ sheet.cell('A1').value = 'Hello';
41
+ sheet.cell('B1').value = 42;
42
+ sheet.cell('C1').value = true;
43
+ sheet.cell('D1').value = new Date();
44
+
45
+ // Write formulas
46
+ sheet.cell('A2').formula = 'SUM(B1:B1)';
47
+
48
+ // Write a 2D array
49
+ sheet.cell('A3').values = [
50
+ ['Name', 'Age', 'City'],
51
+ ['Alice', 30, 'NYC'],
52
+ ['Bob', 25, 'LA'],
53
+ ];
54
+
55
+ // Save to file
56
+ await wb.toFile('output.xlsx');
57
+ ```
58
+
59
+ ## Loading Existing Files
60
+
61
+ ```typescript
62
+ import { Workbook } from '@niicojs/excel';
63
+
64
+ // Load from file
65
+ const wb = await Workbook.fromFile('template.xlsx');
66
+
67
+ // Or load from buffer
68
+ const buffer = await fetch('https://example.com/file.xlsx').then((r) => r.arrayBuffer());
69
+ const wb = await Workbook.fromBuffer(new Uint8Array(buffer));
70
+
71
+ // Read data
72
+ const sheet = wb.sheet('Sheet1');
73
+ console.log(sheet.cell('A1').value); // The cell value
74
+ console.log(sheet.cell('A1').formula); // The formula (if any)
75
+ console.log(sheet.cell('A1').type); // 'string' | 'number' | 'boolean' | 'date' | 'error' | 'empty'
76
+ ```
77
+
78
+ ## Working with Ranges
79
+
80
+ ```typescript
81
+ const sheet = wb.sheet(0);
82
+
83
+ // Read a range
84
+ const values = sheet.range('A1:C10').values; // 2D array
85
+
86
+ // Write to a range
87
+ sheet.range('A1:B2').values = [
88
+ [1, 2],
89
+ [3, 4],
90
+ ];
91
+
92
+ // Get formulas from a range
93
+ const formulas = sheet.range('A1:C10').formulas;
94
+ ```
95
+
96
+ ## Styling Cells
97
+
98
+ ```typescript
99
+ const sheet = wb.sheet(0);
100
+
101
+ // Apply styles to a cell
102
+ sheet.cell('A1').style = {
103
+ bold: true,
104
+ italic: true,
105
+ fontSize: 14,
106
+ fontName: 'Arial',
107
+ fontColor: '#FF0000',
108
+ fill: '#FFFF00',
109
+ border: {
110
+ top: 'thin',
111
+ bottom: 'medium',
112
+ left: 'thin',
113
+ right: 'thin',
114
+ },
115
+ alignment: {
116
+ horizontal: 'center',
117
+ vertical: 'middle',
118
+ wrapText: true,
119
+ },
120
+ numberFormat: '#,##0.00',
121
+ };
122
+
123
+ // Apply styles to a range
124
+ sheet.range('A1:C1').style = { bold: true, fill: '#CCCCCC' };
125
+ ```
126
+
127
+ ## Merged Cells
128
+
129
+ ```typescript
130
+ const sheet = wb.sheet(0);
131
+
132
+ // Merge cells
133
+ sheet.mergeCells('A1:C1');
134
+
135
+ // Or using two addresses
136
+ sheet.mergeCells('A1', 'C1');
137
+
138
+ // Unmerge
139
+ sheet.unmergeCells('A1:C1');
140
+
141
+ // Get all merged regions
142
+ console.log(sheet.mergedCells); // ['A1:C3', 'D5:E10', ...]
143
+ ```
144
+
145
+ ## Column Widths and Row Heights
146
+
147
+ ```typescript
148
+ const sheet = wb.sheet(0);
149
+
150
+ // Set column width (by letter or 0-based index)
151
+ sheet.setColumnWidth('A', 20);
152
+ sheet.setColumnWidth(1, 15); // Column B
153
+
154
+ // Get column width
155
+ const width = sheet.getColumnWidth('A'); // 20 or undefined
156
+
157
+ // Set row height (0-based index)
158
+ sheet.setRowHeight(0, 30); // First row
159
+
160
+ // Get row height
161
+ const height = sheet.getRowHeight(0); // 30 or undefined
162
+ ```
163
+
164
+ ## Freeze Panes
165
+
166
+ ```typescript
167
+ const sheet = wb.sheet(0);
168
+
169
+ // Freeze first row (header)
170
+ sheet.freezePane(1, 0);
171
+
172
+ // Freeze first column
173
+ sheet.freezePane(0, 1);
174
+
175
+ // Freeze first row and first column
176
+ sheet.freezePane(1, 1);
177
+
178
+ // Unfreeze
179
+ sheet.freezePane(0, 0);
180
+
181
+ // Get current freeze pane configuration
182
+ const frozen = sheet.getFrozenPane();
183
+ // { row: 1, col: 0 } or null
184
+ ```
185
+
186
+ ## Sheet Operations
187
+
188
+ ```typescript
189
+ const wb = Workbook.create();
190
+
191
+ // Add sheets
192
+ wb.addSheet('Data');
193
+ wb.addSheet('Summary', 0); // Insert at index 0
194
+
195
+ // Get sheet names
196
+ console.log(wb.sheetNames); // ['Summary', 'Data']
197
+
198
+ // Access sheets
199
+ const sheet = wb.sheet('Data'); // By name
200
+ const sheet = wb.sheet(0); // By index
201
+
202
+ // Rename sheet
203
+ wb.renameSheet('Data', 'RawData');
204
+
205
+ // Copy sheet
206
+ wb.copySheet('RawData', 'RawData_Backup');
207
+
208
+ // Delete sheet
209
+ wb.deleteSheet('Summary');
210
+ ```
211
+
212
+ ## Pivot Tables
213
+
214
+ Create pivot tables with a fluent API:
215
+
216
+ ```typescript
217
+ const wb = await Workbook.fromFile('sales-data.xlsx');
218
+
219
+ // Create a pivot table from source data
220
+ const pivot = wb.createPivotTable({
221
+ name: 'SalesPivot',
222
+ source: 'Data!A1:E100', // Source range with headers
223
+ target: 'Summary!A3', // Where to place the pivot table
224
+ refreshOnLoad: true, // Refresh when file opens (default: true)
225
+ });
226
+
227
+ // Configure fields using fluent API
228
+ pivot
229
+ .addRowField('Region') // Group by region
230
+ .addRowField('Product') // Then by product
231
+ .addColumnField('Year') // Columns by year
232
+ .addValueField('Sales', 'sum', 'Total Sales') // Sum of sales
233
+ .addValueField('Quantity', 'count', 'Order Count') // Count of orders
234
+ .addFilterField('Category'); // Page filter
235
+
236
+ await wb.toFile('report.xlsx');
237
+ ```
238
+
239
+ ### Sorting Fields
240
+
241
+ Sort row or column fields in ascending or descending order:
242
+
243
+ ```typescript
244
+ pivot
245
+ .addRowField('Region')
246
+ .addRowField('Product')
247
+ .addColumnField('Year')
248
+ .sortField('Region', 'asc') // Sort regions A-Z
249
+ .sortField('Year', 'desc'); // Sort years newest first
250
+ ```
251
+
252
+ ### Filtering Fields
253
+
254
+ Filter field values using include or exclude lists:
255
+
256
+ ```typescript
257
+ // Include only specific values
258
+ pivot.addRowField('Region').filterField('Region', { include: ['North', 'South'] });
259
+
260
+ // Exclude specific values
261
+ pivot.addColumnField('Product').filterField('Product', { exclude: ['Discontinued', 'Legacy'] });
262
+ ```
263
+
264
+ ### Value Fields with Number Formats
265
+
266
+ Apply number formats to value fields:
267
+
268
+ ```typescript
269
+ pivot
270
+ .addValueField('Sales', 'sum', 'Total Sales', '$#,##0.00')
271
+ .addValueField('Quantity', 'average', 'Avg Qty', '0.00')
272
+ .addValueField('Margin', 'sum', 'Margin %', '0.00%');
273
+
274
+ // Or using object syntax
275
+ pivot.addValueField({
276
+ field: 'Sales',
277
+ aggregation: 'sum',
278
+ name: 'Total Sales',
279
+ numberFormat: '$#,##0.00',
280
+ });
281
+ ```
282
+
283
+ ### Pivot Table API Reference
284
+
285
+ ```typescript
286
+ // Add fields to different areas
287
+ pivot.addRowField(fieldName: string): PivotTable
288
+ pivot.addColumnField(fieldName: string): PivotTable
289
+ pivot.addValueField(fieldName: string, aggregation?: AggregationType, displayName?: string, numberFormat?: string): PivotTable
290
+ pivot.addValueField(config: PivotValueConfig): PivotTable
291
+ pivot.addFilterField(fieldName: string): PivotTable
292
+
293
+ // Sort a row or column field
294
+ pivot.sortField(fieldName: string, order: 'asc' | 'desc'): PivotTable
295
+
296
+ // Filter field values (use include OR exclude, not both)
297
+ pivot.filterField(fieldName: string, filter: { include?: string[] } | { exclude?: string[] }): PivotTable
298
+
299
+ // Aggregation types
300
+ type AggregationType = 'sum' | 'count' | 'average' | 'min' | 'max';
301
+
302
+ // Value field config object
303
+ interface PivotValueConfig {
304
+ field: string;
305
+ aggregation?: AggregationType; // default: 'sum'
306
+ name?: string; // default: 'Sum of {field}'
307
+ numberFormat?: string;
308
+ }
309
+ ```
310
+
311
+ ## Creating Sheets from Data
312
+
313
+ Create sheets directly from arrays of objects with `addSheetFromData`:
314
+
315
+ ```typescript
316
+ const wb = Workbook.create();
317
+
318
+ // Simple usage - object keys become column headers
319
+ const employees = [
320
+ { name: 'Alice', age: 30, city: 'Paris' },
321
+ { name: 'Bob', age: 25, city: 'London' },
322
+ ];
323
+
324
+ wb.addSheetFromData({
325
+ name: 'Employees',
326
+ data: employees,
327
+ });
328
+
329
+ // Custom column configuration
330
+ wb.addSheetFromData({
331
+ name: 'Custom',
332
+ data: employees,
333
+ columns: [
334
+ { key: 'name', header: 'Full Name' },
335
+ { key: 'age', header: 'Age (years)' },
336
+ { key: 'city', header: 'Location', style: { bold: true } },
337
+ ],
338
+ });
339
+
340
+ // With formulas and styles using RichCellValue
341
+ const orderLines = [
342
+ { product: 'Widget', price: 10, qty: 5, total: { formula: 'B2*C2', style: { bold: true } } },
343
+ { product: 'Gadget', price: 20, qty: 3, total: { formula: 'B3*C3', style: { bold: true } } },
344
+ ];
345
+
346
+ wb.addSheetFromData({
347
+ name: 'Orders',
348
+ data: orderLines,
349
+ });
350
+
351
+ // Other options
352
+ wb.addSheetFromData({
353
+ name: 'Options',
354
+ data: employees,
355
+ headerStyle: false, // Don't bold headers
356
+ startCell: 'B3', // Start at B3 instead of A1
357
+ });
358
+ ```
359
+
360
+ ## Converting Sheets to JSON
361
+
362
+ Convert sheet data back to arrays of objects with `toJson`:
363
+
364
+ ```typescript
365
+ const sheet = wb.sheet('Data');
366
+
367
+ // Using first row as headers
368
+ const data = sheet.toJson();
369
+ // [{ name: 'Alice', age: 30 }, { name: 'Bob', age: 25 }]
370
+
371
+ // Using custom field names (first row is data, not headers)
372
+ const data2 = sheet.toJson({
373
+ fields: ['name', 'age', 'city'],
374
+ });
375
+
376
+ // With TypeScript generics
377
+ interface Person {
378
+ name: string | null;
379
+ age: number | null;
380
+ }
381
+ const people = sheet.toJson<Person>();
382
+
383
+ // Starting from a specific position
384
+ const data3 = sheet.toJson({
385
+ startRow: 2, // Skip first 2 rows (0-based)
386
+ startCol: 1, // Start from column B
387
+ });
388
+
389
+ // Limiting the range
390
+ const data4 = sheet.toJson({
391
+ endRow: 10, // Stop at row 11 (0-based, inclusive)
392
+ endCol: 3, // Only read columns A-D
393
+ });
394
+
395
+ // Continue past empty rows
396
+ const data5 = sheet.toJson({
397
+ stopOnEmptyRow: false, // Default is true
398
+ });
399
+
400
+ // Control how dates are serialized
401
+ const data6 = sheet.toJson({
402
+ dateHandling: 'isoString', // 'jsDate' | 'excelSerial' | 'isoString'
403
+ });
404
+ ```
405
+
406
+ ### Roundtrip Example
407
+
408
+ ```typescript
409
+ // Create from objects
410
+ const originalData = [
411
+ { name: 'Alice', age: 30 },
412
+ { name: 'Bob', age: 25 },
413
+ ];
414
+
415
+ const sheet = wb.addSheetFromData({
416
+ name: 'People',
417
+ data: originalData,
418
+ });
419
+
420
+ // Read back as objects
421
+ const readData = sheet.toJson();
422
+ // readData equals originalData
423
+ ```
424
+
425
+ ## Saving
426
+
427
+ ```typescript
428
+ // Load from file
429
+ const wb = await Workbook.fromFile('template.xlsx');
430
+
431
+ // Or load from buffer
432
+ const buffer = await fetch('https://example.com/file.xlsx').then((r) => r.arrayBuffer());
433
+ const wb2 = await Workbook.fromBuffer(new Uint8Array(buffer));
434
+
435
+ // Read data
436
+ const sheet = wb.sheet('Sheet1');
437
+ console.log(sheet.cell('A1').value); // The cell value
438
+ console.log(sheet.cell('A1').formula); // The formula (if any)
439
+ console.log(sheet.cell('A1').type); // 'string' | 'number' | 'boolean' | 'date' | 'error' | 'empty'
440
+
441
+ // Check if a cell exists without creating it
442
+ const existingCell = sheet.getCellIfExists('A1');
443
+ if (existingCell) {
444
+ console.log(existingCell.value);
445
+ }
446
+ ```
447
+
448
+ ## Type Definitions
449
+
450
+ ```typescript
451
+ // Cell values
452
+ type CellValue = number | string | boolean | Date | null | CellError;
453
+
454
+ interface CellError {
455
+ error: '#NULL!' | '#DIV/0!' | '#VALUE!' | '#REF!' | '#NAME?' | '#NUM!' | '#N/A';
456
+ }
457
+
458
+ // Cell types
459
+ type CellType = 'number' | 'string' | 'boolean' | 'date' | 'error' | 'empty';
460
+
461
+ // Border types
462
+ type BorderType = 'thin' | 'medium' | 'thick' | 'double' | 'dotted' | 'dashed';
463
+
464
+ // Configuration for addSheetFromData
465
+ interface SheetFromDataConfig<T> {
466
+ name: string;
467
+ data: T[];
468
+ columns?: ColumnConfig<T>[];
469
+ headerStyle?: boolean; // Default: true
470
+ startCell?: string; // Default: 'A1'
471
+ }
472
+
473
+ interface ColumnConfig<T> {
474
+ key: keyof T;
475
+ header?: string;
476
+ style?: CellStyle;
477
+ }
478
+
479
+ // Rich cell value for formulas/styles in data
480
+ interface RichCellValue {
481
+ value?: CellValue;
482
+ formula?: string;
483
+ style?: CellStyle;
484
+ }
485
+
486
+ // Configuration for toJson
487
+ interface SheetToJsonConfig {
488
+ fields?: string[];
489
+ startRow?: number;
490
+ startCol?: number;
491
+ endRow?: number;
492
+ endCol?: number;
493
+ stopOnEmptyRow?: boolean; // Default: true
494
+ dateHandling?: 'jsDate' | 'excelSerial' | 'isoString'; // Default: 'jsDate'
495
+ }
496
+
497
+ // Pivot table configuration
498
+ interface PivotTableConfig {
499
+ name: string;
500
+ source: string; // e.g., "Sheet1!A1:D100"
501
+ target: string; // e.g., "Sheet2!A3"
502
+ refreshOnLoad?: boolean; // Default: true
503
+ }
504
+
505
+ // Pivot table value field configuration
506
+ interface PivotValueConfig {
507
+ field: string;
508
+ aggregation?: AggregationType;
509
+ name?: string;
510
+ numberFormat?: string;
511
+ }
512
+
513
+ type AggregationType = 'sum' | 'count' | 'average' | 'min' | 'max';
514
+ type PivotSortOrder = 'asc' | 'desc';
515
+
516
+ interface PivotFieldFilter {
517
+ include?: string[]; // Show only these values
518
+ exclude?: string[]; // Hide these values (cannot use with include)
519
+ }
520
+ ```
521
+
522
+ ## Style Schema
523
+
524
+ Supported style properties (CellStyle):
525
+
526
+ ```typescript
527
+ interface CellStyle {
528
+ bold?: boolean;
529
+ italic?: boolean;
530
+ underline?: boolean | 'single' | 'double';
531
+ strike?: boolean;
532
+ fontSize?: number;
533
+ fontName?: string;
534
+ fontColor?: string; // Hex (RGB/RRGGBB/AARRGGBB)
535
+ fill?: string; // Hex (RGB/RRGGBB/AARRGGBB)
536
+ border?: {
537
+ top?: 'thin' | 'medium' | 'thick' | 'double' | 'dotted' | 'dashed';
538
+ bottom?: 'thin' | 'medium' | 'thick' | 'double' | 'dotted' | 'dashed';
539
+ left?: 'thin' | 'medium' | 'thick' | 'double' | 'dotted' | 'dashed';
540
+ right?: 'thin' | 'medium' | 'thick' | 'double' | 'dotted' | 'dashed';
541
+ };
542
+ alignment?: {
543
+ horizontal?: 'left' | 'center' | 'right' | 'justify';
544
+ vertical?: 'top' | 'middle' | 'bottom';
545
+ wrapText?: boolean;
546
+ textRotation?: number;
547
+ };
548
+ numberFormat?: string; // Excel format code
549
+ }
550
+ ```
551
+
552
+ ## Performance and Large Files
553
+
554
+ Tips for large sheets and high-volume writes:
555
+
556
+ - Prefer `addSheetFromData` for bulk writes when possible.
557
+ - Use `range.getValues({ createMissing: false })` to avoid creating empty cells during reads.
558
+ - Keep shared strings small: prefer numbers/booleans where applicable.
559
+ - Avoid frequent `toBuffer()` calls in loops; batch writes and serialize once.
560
+
561
+ ## Limitations
562
+
563
+ The library focuses on preserving existing structure and editing common parts of workbooks.
564
+
565
+ - Chart editing is not supported (charts are preserved only).
566
+ - Conditional formatting and data validation are preserved but not editable yet.
567
+ - Some advanced Excel features (sparklines, slicers, macros) are preserved only.
568
+
569
+ ## Format Preservation
570
+
571
+ When modifying existing Excel files, this library preserves:
572
+
573
+ - Cell formatting and styles
574
+ - Formulas
575
+ - Charts and images
576
+ - Merged cells
577
+ - Conditional formatting
578
+ - Data validation
579
+ - And other Excel features
580
+
581
+ This is achieved by only modifying what's necessary and keeping the original XML structure intact.
582
+
583
+ ## License
584
+
585
+ MIT