@xlsxflow/core 1.1.4 → 1.1.6

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/dist/index.mjs CHANGED
@@ -56,21 +56,41 @@ class ZipStreamWriter {
56
56
  // Stream data, tracking sizes and CRC32
57
57
  let uncompressedSize = 0;
58
58
  let crc = 0xffffffff;
59
- const crcStream = new TransformStream({
60
- // CompressionStream takes every write at once, so the input is held back here instead,
61
- // while nobody reads the output
62
- transform: async (chunk, controller) => {
63
- await this.roomInQueue();
64
- uncompressedSize += chunk.length;
65
- crc = crc32Update(crc, chunk);
66
- controller.enqueue(chunk);
59
+ const compressor = new CompressionStream('deflate-raw');
60
+ const writer = compressor.writable.getWriter();
61
+ const reader = compressor.readable.getReader();
62
+ const input = inputStream.getReader();
63
+ // Node's and Bun's CompressionStream accept thousands of writes without backpressure, so only a few
64
+ // chunks are fed ahead: a write resolves once the chunk is compressed, which waits while nobody reads
65
+ // the output. Rows are then only pulled as fast as the ZIP is consumed.
66
+ // A few writes in flight keep the compressor busy while the next chunk is built
67
+ const inFlight = [];
68
+ const feed = (async () => {
69
+ try {
70
+ while (true) {
71
+ const { done, value } = await input.read();
72
+ if (done)
73
+ break;
74
+ await this.roomInQueue();
75
+ uncompressedSize += value.length;
76
+ crc = crc32Update(crc, value);
77
+ const written = writer.write(value);
78
+ written.catch(() => { }); // after a failure, writes nobody awaits any more must not go unhandled
79
+ inFlight.push(written);
80
+ if (inFlight.length >= 4)
81
+ await inFlight.shift();
82
+ }
83
+ await Promise.all(inFlight);
84
+ await writer.close();
67
85
  }
68
- });
86
+ catch (err) {
87
+ // Stop the source too (a row generator's finally runs, a database cursor closes)
88
+ await input.cancel(err).catch(() => { });
89
+ await writer.abort(err).catch(() => { });
90
+ throw err;
91
+ }
92
+ })();
69
93
  let compressedSize = 0;
70
- const reader = inputStream
71
- .pipeThrough(crcStream)
72
- .pipeThrough(new CompressionStream('deflate-raw'))
73
- .getReader();
74
94
  try {
75
95
  while (true) {
76
96
  const { done, value } = await reader.read();
@@ -79,10 +99,12 @@ class ZipStreamWriter {
79
99
  compressedSize += value.length;
80
100
  await this.pushChunk(value);
81
101
  }
102
+ await feed;
82
103
  }
83
104
  catch (err) {
84
- // Stop the source too (a row generator's finally runs, a database cursor closes)
85
105
  await reader.cancel(err).catch(() => { });
106
+ await input.cancel(err).catch(() => { });
107
+ await feed.catch(() => { });
86
108
  throw err;
87
109
  }
88
110
  crc = (crc ^ 0xffffffff) >>> 0;
@@ -3856,6 +3878,10 @@ class SheetWriter {
3856
3878
  const box = parseRange$1(table.ref, 'table range');
3857
3879
  if (boxes.some(b => overlaps(b, box)))
3858
3880
  throw new Error(`Table "${table.name}" overlaps another table on sheet "${sheet.name}".`);
3881
+ // Excel tables cannot hold merged cells: it repairs the file by removing the table
3882
+ const merge = sheet.options.mergeCells?.find(ref => overlaps(parseRange$1(ref, 'merge range'), box));
3883
+ if (merge)
3884
+ throw new Error(`Merge "${merge}" overlaps table "${table.name}" on sheet "${sheet.name}"; Excel tables cannot contain merged cells.`);
3859
3885
  boxes.push(box);
3860
3886
  const first = colIndex(m[1]);
3861
3887
  const width = colIndex(m[3]) - first + 1;
@@ -4357,8 +4383,18 @@ function createBlobReader(blob) {
4357
4383
  return new Uint8Array(await slice.arrayBuffer());
4358
4384
  },
4359
4385
  stream(offset, length) {
4360
- const slice = blob.slice(offset, offset + length);
4361
- return slice.stream();
4386
+ // Bun's Blob.slice(start, end).stream() runs on to the end of the original blob, so stop at length
4387
+ let left = length;
4388
+ return blob.slice(offset, offset + length).stream().pipeThrough(new TransformStream({
4389
+ start(controller) { if (left <= 0)
4390
+ controller.terminate(); },
4391
+ transform(chunk, controller) {
4392
+ controller.enqueue(chunk.length > left ? chunk.subarray(0, left) : chunk);
4393
+ left -= chunk.length;
4394
+ if (left <= 0)
4395
+ controller.terminate();
4396
+ }
4397
+ }));
4362
4398
  },
4363
4399
  async close() { }
4364
4400
  };