@cj-tech-master/excelts 1.4.5 → 1.5.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 (49) hide show
  1. package/dist/browser/excelts.iife.js +454 -159
  2. package/dist/browser/excelts.iife.js.map +1 -1
  3. package/dist/browser/excelts.iife.min.js +28 -28
  4. package/dist/cjs/doc/anchor.js +25 -11
  5. package/dist/cjs/doc/cell.js +75 -43
  6. package/dist/cjs/doc/column.js +74 -22
  7. package/dist/cjs/doc/defined-names.js +53 -7
  8. package/dist/cjs/doc/image.js +11 -8
  9. package/dist/cjs/doc/range.js +64 -28
  10. package/dist/cjs/doc/row.js +72 -31
  11. package/dist/cjs/doc/table.js +3 -5
  12. package/dist/cjs/doc/workbook.js +30 -6
  13. package/dist/cjs/doc/worksheet.js +165 -41
  14. package/dist/cjs/utils/sheet-utils.js +3 -1
  15. package/dist/cjs/utils/unzip/extract.js +30 -82
  16. package/dist/cjs/utils/unzip/index.js +18 -2
  17. package/dist/cjs/utils/unzip/zip-parser.js +458 -0
  18. package/dist/esm/doc/anchor.js +25 -11
  19. package/dist/esm/doc/cell.js +75 -43
  20. package/dist/esm/doc/column.js +74 -22
  21. package/dist/esm/doc/defined-names.js +53 -7
  22. package/dist/esm/doc/image.js +11 -8
  23. package/dist/esm/doc/range.js +64 -28
  24. package/dist/esm/doc/row.js +72 -31
  25. package/dist/esm/doc/table.js +3 -5
  26. package/dist/esm/doc/workbook.js +30 -6
  27. package/dist/esm/doc/worksheet.js +165 -41
  28. package/dist/esm/utils/sheet-utils.js +3 -1
  29. package/dist/esm/utils/unzip/extract.js +28 -82
  30. package/dist/esm/utils/unzip/index.js +17 -2
  31. package/dist/esm/utils/unzip/zip-parser.js +451 -0
  32. package/dist/types/doc/anchor.d.ts +14 -7
  33. package/dist/types/doc/cell.d.ts +78 -37
  34. package/dist/types/doc/column.d.ts +72 -36
  35. package/dist/types/doc/defined-names.d.ts +11 -8
  36. package/dist/types/doc/image.d.ts +29 -12
  37. package/dist/types/doc/pivot-table.d.ts +1 -1
  38. package/dist/types/doc/range.d.ts +15 -4
  39. package/dist/types/doc/row.d.ts +78 -40
  40. package/dist/types/doc/table.d.ts +21 -36
  41. package/dist/types/doc/workbook.d.ts +54 -34
  42. package/dist/types/doc/worksheet.d.ts +255 -83
  43. package/dist/types/stream/xlsx/worksheet-reader.d.ts +3 -5
  44. package/dist/types/types.d.ts +86 -26
  45. package/dist/types/utils/col-cache.d.ts +11 -8
  46. package/dist/types/utils/unzip/extract.d.ts +16 -14
  47. package/dist/types/utils/unzip/index.d.ts +15 -1
  48. package/dist/types/utils/unzip/zip-parser.d.ts +92 -0
  49. package/package.json +1 -1
@@ -125,19 +125,26 @@ class Worksheet {
125
125
  }
126
126
  name = name.substring(0, 31);
127
127
  }
128
- if (this._workbook._worksheets.find((ws) => ws && ws.name.toLowerCase() === name.toLowerCase())) {
128
+ if (this._workbook.worksheets.find(ws => ws && ws.name.toLowerCase() === name.toLowerCase())) {
129
129
  throw new Error(`Worksheet name already exists: ${name}`);
130
130
  }
131
131
  this._name = name;
132
132
  }
133
+ /**
134
+ * The workbook that contains this worksheet
135
+ */
133
136
  get workbook() {
134
137
  return this._workbook;
135
138
  }
136
- // when you're done with this worksheet, call this to remove from workbook
139
+ /**
140
+ * When you're done with this worksheet, call this to remove from workbook
141
+ */
137
142
  destroy() {
138
143
  this._workbook.removeWorksheetEx(this);
139
144
  }
140
- // Get the bounding range of the cells in this worksheet
145
+ /**
146
+ * Get the bounding range of the cells in this worksheet
147
+ */
141
148
  get dimensions() {
142
149
  const dimensions = new Range();
143
150
  this._rows.forEach(row => {
@@ -152,16 +159,22 @@ class Worksheet {
152
159
  }
153
160
  // =========================================================================
154
161
  // Columns
155
- // get the current columns array.
162
+ /**
163
+ * Get the current columns array
164
+ */
156
165
  get columns() {
157
166
  return this._columns;
158
167
  }
159
- // set the columns from an array of column definitions.
160
- // Note: any headers defined will overwrite existing values.
168
+ /**
169
+ * Add column headers and define column keys and widths.
170
+ *
171
+ * Note: these column structures are a workbook-building convenience only,
172
+ * apart from the column width, they will not be fully persisted.
173
+ */
161
174
  set columns(value) {
162
175
  // calculate max header row count
163
176
  this._headerRowCount = value.reduce((pv, cv) => {
164
- const headerCount = (cv.header && 1) || (cv.headers && cv.headers.length) || 0;
177
+ const headerCount = Array.isArray(cv.header) ? cv.header.length : cv.header ? 1 : 0;
165
178
  return Math.max(pv, headerCount);
166
179
  }, 0);
167
180
  // construct Column objects
@@ -185,7 +198,9 @@ class Worksheet {
185
198
  eachColumnKey(f) {
186
199
  Object.keys(this._keys).forEach(key => f(this._keys[key], key));
187
200
  }
188
- // get a single column by col number. If it doesn't exist, create it and any gaps before it
201
+ /**
202
+ * Access an individual column by key, letter and 1-based column number
203
+ */
189
204
  getColumn(c) {
190
205
  let colNum;
191
206
  if (typeof c === "string") {
@@ -211,18 +226,26 @@ class Worksheet {
211
226
  }
212
227
  return this._columns[colNum - 1];
213
228
  }
229
+ /**
230
+ * Cut one or more columns (columns to the right are shifted left)
231
+ * and optionally insert more
232
+ *
233
+ * If column properties have been defined, they will be cut or moved accordingly
234
+ *
235
+ * Known Issue: If a splice causes any merged cells to move, the results may be unpredictable
236
+ *
237
+ * Also: If the worksheet has more rows than values in the column inserts,
238
+ * the rows will still be shifted as if the values existed
239
+ */
214
240
  spliceColumns(start, count, ...inserts) {
215
241
  const rows = this._rows;
216
242
  const nRows = rows.length;
217
243
  if (inserts.length > 0) {
218
244
  // must iterate over all rows whether they exist yet or not
219
245
  for (let i = 0; i < nRows; i++) {
220
- const rowArguments = [start, count];
221
- inserts.forEach(insert => {
222
- rowArguments.push(insert[i] || null);
223
- });
246
+ const insertValues = inserts.map(insert => insert[i] || null);
224
247
  const row = this.getRow(i + 1);
225
- row.splice(...rowArguments);
248
+ row.splice(start, count, ...insertValues);
226
249
  }
227
250
  }
228
251
  else {
@@ -248,26 +271,35 @@ class Worksheet {
248
271
  }
249
272
  }
250
273
  for (let i = start; i < start + inserts.length; i++) {
251
- this.getColumn(i).defn = null;
274
+ this.getColumn(i).defn = undefined;
252
275
  }
253
276
  // account for defined names
254
277
  this.workbook.definedNames.spliceColumns(this.name, start, count, inserts.length);
255
278
  }
279
+ /**
280
+ * Get the last column in a worksheet
281
+ */
256
282
  get lastColumn() {
257
283
  return this.getColumn(this.columnCount);
258
284
  }
285
+ /**
286
+ * The total column size of the document. Equal to the maximum cell count from all of the rows
287
+ */
259
288
  get columnCount() {
260
289
  let maxCount = 0;
261
- this.eachRow((row) => {
290
+ this.eachRow(row => {
262
291
  maxCount = Math.max(maxCount, row.cellCount);
263
292
  });
264
293
  return maxCount;
265
294
  }
295
+ /**
296
+ * A count of the number of columns that have values
297
+ */
266
298
  get actualColumnCount() {
267
299
  // performance nightmare - for each row, counts all the columns used
268
300
  const counts = [];
269
301
  let count = 0;
270
- this.eachRow((row) => {
302
+ this.eachRow(row => {
271
303
  row.eachCell(({ col }) => {
272
304
  if (!counts[col]) {
273
305
  counts[col] = true;
@@ -294,23 +326,41 @@ class Worksheet {
294
326
  get _nextRow() {
295
327
  return this._lastRowNumber + 1;
296
328
  }
329
+ /**
330
+ * Get the last editable row in a worksheet (or undefined if there are none)
331
+ */
297
332
  get lastRow() {
298
333
  if (this._rows.length) {
299
334
  return this._rows[this._rows.length - 1];
300
335
  }
301
336
  return undefined;
302
337
  }
303
- // find a row (if exists) by row number
338
+ /**
339
+ * Tries to find and return row for row number, else undefined
340
+ *
341
+ * @param r - The 1-indexed row number
342
+ */
304
343
  findRow(r) {
305
344
  return this._rows[r - 1];
306
345
  }
307
- // find multiple rows (if exists) by row number
346
+ /**
347
+ * Tries to find and return rows for row number start and length, else undefined
348
+ *
349
+ * @param start - The 1-indexed starting row number
350
+ * @param length - The length of the expected array
351
+ */
308
352
  findRows(start, length) {
309
353
  return this._rows.slice(start - 1, start - 1 + length);
310
354
  }
355
+ /**
356
+ * The total row size of the document. Equal to the row number of the last row that has values.
357
+ */
311
358
  get rowCount() {
312
359
  return this._lastRowNumber;
313
360
  }
361
+ /**
362
+ * A count of the number of rows that have values. If a mid-document row is empty, it will not be included in the count.
363
+ */
314
364
  get actualRowCount() {
315
365
  // counts actual rows that have actual data
316
366
  let count = 0;
@@ -319,7 +369,9 @@ class Worksheet {
319
369
  });
320
370
  return count;
321
371
  }
322
- // get a row by row number.
372
+ /**
373
+ * Get or create row by 1-based index
374
+ */
323
375
  getRow(r) {
324
376
  let row = this._rows[r - 1];
325
377
  if (!row) {
@@ -327,7 +379,9 @@ class Worksheet {
327
379
  }
328
380
  return row;
329
381
  }
330
- // get multiple rows by row number.
382
+ /**
383
+ * Get or create rows by 1-based index
384
+ */
331
385
  getRows(start, length) {
332
386
  if (length < 1) {
333
387
  return undefined;
@@ -338,6 +392,10 @@ class Worksheet {
338
392
  }
339
393
  return rows;
340
394
  }
395
+ /**
396
+ * Add a couple of Rows by key-value, after the last current row, using the column keys,
397
+ * or add a row by contiguous Array (assign to columns A, B & C)
398
+ */
341
399
  addRow(value, style = "n") {
342
400
  const rowNo = this._nextRow;
343
401
  const row = this.getRow(rowNo);
@@ -345,6 +403,9 @@ class Worksheet {
345
403
  this._setStyleOption(rowNo, style[0] === "i" ? style : "n");
346
404
  return row;
347
405
  }
406
+ /**
407
+ * Add multiple rows by providing an array of arrays or key-value pairs
408
+ */
348
409
  addRows(value, style = "n") {
349
410
  const rows = [];
350
411
  value.forEach(row => {
@@ -352,11 +413,19 @@ class Worksheet {
352
413
  });
353
414
  return rows;
354
415
  }
416
+ /**
417
+ * Insert a Row by key-value, at the position (shifting down all rows from position),
418
+ * using the column keys, or add a row by contiguous Array (assign to columns A, B & C)
419
+ */
355
420
  insertRow(pos, value, style = "n") {
356
421
  this.spliceRows(pos, 0, value);
357
422
  this._setStyleOption(pos, style);
358
423
  return this.getRow(pos);
359
424
  }
425
+ /**
426
+ * Insert multiple rows at position (shifting down all rows from position)
427
+ * by providing an array of arrays or key-value pairs
428
+ */
360
429
  insertRows(pos, values, style = "n") {
361
430
  this.spliceRows(pos, 0, ...values);
362
431
  if (style !== "n") {
@@ -390,6 +459,9 @@ class Worksheet {
390
459
  });
391
460
  rDst.height = rSrc.height;
392
461
  }
462
+ /**
463
+ * Duplicate rows and insert new rows
464
+ */
393
465
  duplicateRow(rowNum, count, insert = false) {
394
466
  // create count duplicates of rowNum
395
467
  // either inserting new or overwriting existing rows
@@ -406,6 +478,12 @@ class Worksheet {
406
478
  });
407
479
  }
408
480
  }
481
+ /**
482
+ * Cut one or more rows (rows below are shifted up)
483
+ * and optionally insert more
484
+ *
485
+ * Known Issue: If a splice causes any merged cells to move, the results may be unpredictable
486
+ */
409
487
  spliceRows(start, count, ...inserts) {
410
488
  // same problem as row.splice, except worse.
411
489
  const nKeep = start + count;
@@ -448,10 +526,10 @@ class Worksheet {
448
526
  rSrc.eachCell({ includeEmpty: true }, (cell, colNumber) => {
449
527
  rDst.getCell(colNumber).style = cell.style;
450
528
  // remerge cells accounting for insert offset
451
- if (cell._value.constructor.name === "MergeValue") {
452
- const cellToBeMerged = this.getRow(cell._row._number + nInserts).getCell(colNumber);
453
- const prevMaster = cell._value._master;
454
- const newMaster = this.getRow(prevMaster._row._number + nInserts).getCell(prevMaster._column._number);
529
+ if (cell.type === Enums.ValueType.Merge) {
530
+ const cellToBeMerged = this.getRow(cell.row + nInserts).getCell(colNumber);
531
+ const prevMaster = cell.master;
532
+ const newMaster = this.getRow(prevMaster.row + nInserts).getCell(prevMaster.col);
455
533
  cellToBeMerged.merge(newMaster);
456
534
  }
457
535
  });
@@ -470,31 +548,33 @@ class Worksheet {
470
548
  // account for defined names
471
549
  this.workbook.definedNames.spliceRows(this.name, start, count, nInserts);
472
550
  }
473
- eachRow(optionsOrIteratee, maybeIteratee) {
551
+ eachRow(optOrCallback, maybeCallback) {
474
552
  let options;
475
- let iteratee;
476
- if (typeof optionsOrIteratee === "function") {
477
- iteratee = optionsOrIteratee;
553
+ let callback;
554
+ if (typeof optOrCallback === "function") {
555
+ callback = optOrCallback;
478
556
  }
479
557
  else {
480
- options = optionsOrIteratee;
481
- iteratee = maybeIteratee;
558
+ options = optOrCallback;
559
+ callback = maybeCallback;
482
560
  }
483
561
  if (options && options.includeEmpty) {
484
562
  const n = this._rows.length;
485
563
  for (let i = 1; i <= n; i++) {
486
- iteratee(this.getRow(i), i);
564
+ callback(this.getRow(i), i);
487
565
  }
488
566
  }
489
567
  else {
490
568
  this._rows.forEach(row => {
491
569
  if (row && row.hasValues) {
492
- iteratee(row, row.number);
570
+ callback(row, row.number);
493
571
  }
494
572
  });
495
573
  }
496
574
  }
497
- // return all rows as sparse array
575
+ /**
576
+ * Return all rows as sparse array
577
+ */
498
578
  getSheetValues() {
499
579
  const rows = [];
500
580
  this._rows.forEach(row => {
@@ -506,13 +586,17 @@ class Worksheet {
506
586
  }
507
587
  // =========================================================================
508
588
  // Cells
509
- // returns the cell at [r,c] or address given by r. If not found, return undefined
589
+ /**
590
+ * Returns the cell at [r,c] or address given by r. If not found, return undefined
591
+ */
510
592
  findCell(r, c) {
511
593
  const address = colCache.getAddress(r, c);
512
594
  const row = this._rows[address.row - 1];
513
595
  return row ? row.findCell(address.col) : undefined;
514
596
  }
515
- // return the cell at [r,c] or address given by r. If not found, create a new one.
597
+ /**
598
+ * Get or create cell at [r,c] or address given by r
599
+ */
516
600
  getCell(r, c) {
517
601
  const address = colCache.getAddress(r, c);
518
602
  const row = this.getRow(address.row);
@@ -520,7 +604,15 @@ class Worksheet {
520
604
  }
521
605
  // =========================================================================
522
606
  // Merge
523
- // convert the range defined by ['tl:br'], [tl,br] or [t,l,b,r] into a single 'merged' cell
607
+ /**
608
+ * Merge cells, either:
609
+ *
610
+ * tlbr string, e.g. `'A4:B5'`
611
+ *
612
+ * tl string, br string, e.g. `'G10', 'H11'`
613
+ *
614
+ * t, l, b, r numbers, e.g. `10,11,12,13`
615
+ */
524
616
  mergeCells(...cells) {
525
617
  const dimensions = new Range(cells);
526
618
  this._mergeCellsInternal(dimensions);
@@ -565,9 +657,11 @@ class Worksheet {
565
657
  // return true if this._merges has a merge object
566
658
  return Object.values(this._merges).some(Boolean);
567
659
  }
568
- // scan the range defined by ['tl:br'], [tl,br] or [t,l,b,r] and if any cell is part of a merge,
569
- // un-merge the group. Note this function can affect multiple merges and merge-blocks are
570
- // atomic - either they're all merged or all un-merged.
660
+ /**
661
+ * Scan the range and if any cell is part of a merge, un-merge the group.
662
+ * Note this function can affect multiple merges and merge-blocks are
663
+ * atomic - either they're all merged or all un-merged.
664
+ */
571
665
  unMergeCells(...cells) {
572
666
  const dimensions = new Range(cells);
573
667
  // find any cells in that range and unmerge them
@@ -616,12 +710,14 @@ class Worksheet {
616
710
  for (let r = top; r <= bottom; r++) {
617
711
  for (let c = left; c <= right; c++) {
618
712
  if (first) {
619
- this.getCell(r, c).value = {
713
+ const cell = this.getCell(r, c);
714
+ const formulaValue = {
620
715
  shareType,
621
716
  formula,
622
717
  ref: range,
623
718
  result: getResult(r, c)
624
719
  };
720
+ cell.value = formulaValue;
625
721
  first = false;
626
722
  }
627
723
  else {
@@ -637,6 +733,10 @@ class Worksheet {
637
733
  }
638
734
  // =========================================================================
639
735
  // Images
736
+ /**
737
+ * Using the image id from `Workbook.addImage`,
738
+ * embed an image within the worksheet to cover a range
739
+ */
640
740
  addImage(imageId, range) {
641
741
  const model = {
642
742
  type: "image",
@@ -648,6 +748,9 @@ class Worksheet {
648
748
  getImages() {
649
749
  return this._media.filter(m => m.type === "image");
650
750
  }
751
+ /**
752
+ * Using the image id from `Workbook.addImage`, set the background to the worksheet
753
+ */
651
754
  addBackgroundImage(imageId) {
652
755
  const model = {
653
756
  type: "background",
@@ -661,6 +764,9 @@ class Worksheet {
661
764
  }
662
765
  // =========================================================================
663
766
  // Worksheet Protection
767
+ /**
768
+ * Protect the worksheet with optional password and options
769
+ */
664
770
  protect(password, options) {
665
771
  // TODO: make this function truly async
666
772
  // perhaps marshal to worker thread or something
@@ -695,17 +801,29 @@ class Worksheet {
695
801
  }
696
802
  // =========================================================================
697
803
  // Tables
804
+ /**
805
+ * Add a new table and return a reference to it
806
+ */
698
807
  addTable(model) {
699
808
  const table = new Table(this, model);
700
809
  this.tables[model.name] = table;
701
810
  return table;
702
811
  }
812
+ /**
813
+ * Fetch table by name
814
+ */
703
815
  getTable(name) {
704
816
  return this.tables[name];
705
817
  }
818
+ /**
819
+ * Delete table by name
820
+ */
706
821
  removeTable(name) {
707
822
  delete this.tables[name];
708
823
  }
824
+ /**
825
+ * Fetch all tables in the worksheet
826
+ */
709
827
  getTables() {
710
828
  return Object.values(this.tables);
711
829
  }
@@ -721,9 +839,15 @@ Please leave feedback at https://github.com/excelts/excelts/discussions/2575`);
721
839
  }
722
840
  // ===========================================================================
723
841
  // Conditional Formatting
842
+ /**
843
+ * Add conditional formatting rules
844
+ */
724
845
  addConditionalFormatting(cf) {
725
846
  this.conditionalFormattings.push(cf);
726
847
  }
848
+ /**
849
+ * Delete conditional formatting rules
850
+ */
727
851
  removeConditionalFormatting(filter) {
728
852
  if (typeof filter === "number") {
729
853
  this.conditionalFormattings.splice(filter, 1);
@@ -757,7 +881,7 @@ Please leave feedback at https://github.com/excelts/excelts/discussions/2575`);
757
881
  };
758
882
  // =================================================
759
883
  // columns
760
- model.cols = Column.toModel(this.columns);
884
+ model.cols = Column.toModel(this.columns || []);
761
885
  // ==========================================================
762
886
  // Rows
763
887
  const rows = (model.rows = []);
@@ -112,7 +112,9 @@ function formatValue(value, fmt, dateFormat) {
112
112
  */
113
113
  function getCellDisplayText(cell, dateFormat) {
114
114
  const value = cell.value;
115
- const fmt = cell.numFmt || "General";
115
+ const numFmt = cell.numFmt;
116
+ // Extract format code string from numFmt (which can be string or NumFmt object)
117
+ const fmt = typeof numFmt === "string" ? numFmt : (numFmt?.formatCode ?? "General");
116
118
  // Null/undefined
117
119
  if (value == null) {
118
120
  return "";
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * Simple ZIP extraction utilities
3
3
  * Provides easy-to-use Promise-based API for extracting ZIP files
4
+ * Works in both Node.js and browser environments
4
5
  */
5
- import { Readable } from "stream";
6
- import { createParse } from "./parse.js";
6
+ import { ZipParser } from "./zip-parser.js";
7
7
  /**
8
8
  * Extract all files from a ZIP buffer
9
9
  *
10
- * @param zipData - ZIP file data as Buffer or Uint8Array
10
+ * @param zipData - ZIP file data as Buffer, Uint8Array, or ArrayBuffer
11
11
  * @returns Map of file paths to their content
12
12
  *
13
13
  * @example
@@ -24,40 +24,24 @@ import { createParse } from "./parse.js";
24
24
  */
25
25
  export async function extractAll(zipData) {
26
26
  const files = new Map();
27
- const buffer = Buffer.isBuffer(zipData) ? zipData : Buffer.from(zipData);
28
- const parse = createParse({ forceStream: true });
29
- const stream = Readable.from([buffer]);
30
- stream.pipe(parse);
31
- for await (const entry of parse) {
32
- const zipEntry = entry;
33
- const isDirectory = zipEntry.type === "Directory";
34
- if (isDirectory) {
35
- files.set(zipEntry.path, {
36
- path: zipEntry.path,
37
- data: Buffer.alloc(0),
38
- isDirectory: true,
39
- size: 0
40
- });
41
- zipEntry.autodrain();
42
- }
43
- else {
44
- const data = await zipEntry.buffer();
45
- files.set(zipEntry.path, {
46
- path: zipEntry.path,
47
- data,
48
- isDirectory: false,
49
- size: data.length
50
- });
51
- }
27
+ const parser = new ZipParser(zipData);
28
+ for (const entry of parser.getEntries()) {
29
+ const data = await parser.extract(entry.path);
30
+ files.set(entry.path, {
31
+ path: entry.path,
32
+ data: data || new Uint8Array(0),
33
+ isDirectory: entry.isDirectory,
34
+ size: entry.uncompressedSize
35
+ });
52
36
  }
53
37
  return files;
54
38
  }
55
39
  /**
56
40
  * Extract a single file from a ZIP buffer
57
41
  *
58
- * @param zipData - ZIP file data as Buffer or Uint8Array
42
+ * @param zipData - ZIP file data as Buffer, Uint8Array, or ArrayBuffer
59
43
  * @param filePath - Path of the file to extract
60
- * @returns File content as Buffer, or null if not found
44
+ * @returns File content as Uint8Array, or null if not found
61
45
  *
62
46
  * @example
63
47
  * ```ts
@@ -66,31 +50,18 @@ export async function extractAll(zipData) {
66
50
  * const zipData = fs.readFileSync("archive.zip");
67
51
  * const content = await extractFile(zipData, "readme.txt");
68
52
  * if (content) {
69
- * console.log(content.toString("utf-8"));
53
+ * console.log(new TextDecoder().decode(content));
70
54
  * }
71
55
  * ```
72
56
  */
73
57
  export async function extractFile(zipData, filePath) {
74
- const buffer = Buffer.isBuffer(zipData) ? zipData : Buffer.from(zipData);
75
- const parse = createParse({ forceStream: true });
76
- const stream = Readable.from([buffer]);
77
- stream.pipe(parse);
78
- for await (const entry of parse) {
79
- const zipEntry = entry;
80
- if (zipEntry.path === filePath) {
81
- if (zipEntry.type === "Directory") {
82
- return Buffer.alloc(0);
83
- }
84
- return zipEntry.buffer();
85
- }
86
- zipEntry.autodrain();
87
- }
88
- return null;
58
+ const parser = new ZipParser(zipData);
59
+ return parser.extract(filePath);
89
60
  }
90
61
  /**
91
62
  * List all file paths in a ZIP buffer (without extracting content)
92
63
  *
93
- * @param zipData - ZIP file data as Buffer or Uint8Array
64
+ * @param zipData - ZIP file data as Buffer, Uint8Array, or ArrayBuffer
94
65
  * @returns Array of file paths
95
66
  *
96
67
  * @example
@@ -103,22 +74,13 @@ export async function extractFile(zipData, filePath) {
103
74
  * ```
104
75
  */
105
76
  export async function listFiles(zipData) {
106
- const paths = [];
107
- const buffer = Buffer.isBuffer(zipData) ? zipData : Buffer.from(zipData);
108
- const parse = createParse({ forceStream: true });
109
- const stream = Readable.from([buffer]);
110
- stream.pipe(parse);
111
- for await (const entry of parse) {
112
- const zipEntry = entry;
113
- paths.push(zipEntry.path);
114
- zipEntry.autodrain();
115
- }
116
- return paths;
77
+ const parser = new ZipParser(zipData);
78
+ return parser.listFiles();
117
79
  }
118
80
  /**
119
81
  * Iterate over ZIP entries with a callback (memory efficient for large ZIPs)
120
82
  *
121
- * @param zipData - ZIP file data as Buffer or Uint8Array
83
+ * @param zipData - ZIP file data as Buffer, Uint8Array, or ArrayBuffer
122
84
  * @param callback - Async callback for each entry, return false to stop iteration
123
85
  *
124
86
  * @example
@@ -128,33 +90,17 @@ export async function listFiles(zipData) {
128
90
  * await forEachEntry(zipData, async (path, getData) => {
129
91
  * if (path.endsWith(".xml")) {
130
92
  * const content = await getData();
131
- * console.log(content.toString("utf-8"));
93
+ * console.log(new TextDecoder().decode(content));
132
94
  * }
133
95
  * return true; // continue iteration
134
96
  * });
135
97
  * ```
136
98
  */
137
99
  export async function forEachEntry(zipData, callback) {
138
- const buffer = Buffer.isBuffer(zipData) ? zipData : Buffer.from(zipData);
139
- const parse = createParse({ forceStream: true });
140
- const stream = Readable.from([buffer]);
141
- stream.pipe(parse);
142
- for await (const entry of parse) {
143
- const zipEntry = entry;
144
- let dataPromise = null;
145
- const getData = () => {
146
- if (!dataPromise) {
147
- dataPromise = zipEntry.buffer();
148
- }
149
- return dataPromise;
150
- };
151
- const shouldContinue = await callback(zipEntry.path, getData, zipEntry);
152
- // If callback didn't read data, drain it
153
- if (!dataPromise) {
154
- zipEntry.autodrain();
155
- }
156
- if (shouldContinue === false) {
157
- break;
158
- }
159
- }
100
+ const parser = new ZipParser(zipData);
101
+ await parser.forEach(async (entry, getData) => {
102
+ return callback(entry.path, getData, entry);
103
+ });
160
104
  }
105
+ // Re-export ZipParser for advanced usage
106
+ export { ZipParser } from "./zip-parser.js";
@@ -1,8 +1,23 @@
1
1
  /**
2
2
  * Unzip utilities for parsing ZIP archives
3
+ *
4
+ * Two APIs are provided:
5
+ *
6
+ * 1. **Stream-based API** (Node.js only):
7
+ * - `Parse`, `createParse` - Parse ZIP files as a stream
8
+ * - Best for large files where you don't want to load entire file into memory
9
+ * - Requires Node.js `stream` module
10
+ *
11
+ * 2. **Buffer-based API** (Browser + Node.js):
12
+ * - `extractAll`, `extractFile`, `listFiles`, `forEachEntry`, `ZipParser`
13
+ * - Works in both Node.js and browser environments
14
+ * - Uses native `DecompressionStream` in browser, `zlib` in Node.js
15
+ * - Best for files already loaded into memory (ArrayBuffer, Uint8Array)
16
+ *
3
17
  * Original source: https://github.com/ZJONSSON/node-unzipper
4
18
  * License: MIT
5
19
  */
20
+ // Stream-based API (Node.js only - requires stream module)
6
21
  export { Parse, createParse } from "./parse.js";
7
22
  export { PullStream } from "./pull-stream.js";
8
23
  export { NoopStream } from "./noop-stream.js";
@@ -10,5 +25,5 @@ export { bufferStream } from "./buffer-stream.js";
10
25
  export { parse as parseBuffer } from "./parse-buffer.js";
11
26
  export { parseDateTime } from "./parse-datetime.js";
12
27
  export { parseExtraField } from "./parse-extra-field.js";
13
- // Simple extraction API
14
- export { extractAll, extractFile, listFiles, forEachEntry } from "./extract.js";
28
+ // Buffer-based API (Browser + Node.js - cross-platform)
29
+ export { extractAll, extractFile, listFiles, forEachEntry, ZipParser } from "./extract.js";