documonster 0.20.2 → 0.21.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/README.md +1 -1
- package/README_zh.md +1 -1
- package/THIRD_PARTY_NOTICES.md +34 -2
- package/dist/esm/modules/archive/core/byte-queue.js +2 -1
- package/dist/esm/modules/archive/unzip/stream.browser.js +6 -0
- package/dist/esm/modules/csv/parse/async.js +2 -1
- package/dist/esm/modules/excel/bridge/csv-bridge.js +1 -1
- package/dist/esm/modules/excel/core/xlsx-io.browser.js +5 -9
- package/dist/esm/modules/excel/core/xlsx-io.js +1 -3
- package/dist/esm/modules/excel/core/xlsx-stream.js +3 -1
- package/dist/esm/modules/excel/stream/workbook-reader.browser.js +10 -10
- package/dist/esm/modules/excel/stream/workbook-reader.js +30 -6
- package/dist/esm/modules/stream/browser/collector.js +3 -5
- package/dist/esm/modules/stream/browser/compose.js +13 -228
- package/dist/esm/modules/stream/browser/duplex-pair.js +71 -0
- package/dist/esm/modules/stream/browser/factories.js +35 -217
- package/dist/esm/modules/stream/browser/pipeline.js +43 -478
- package/dist/esm/modules/stream/browser/utils.js +21 -247
- package/dist/esm/modules/stream/browser/web.js +25 -0
- package/dist/esm/modules/stream/buffered-stream.js +8 -6
- package/dist/esm/modules/stream/core/factories.js +166 -0
- package/dist/esm/modules/stream/core/pipeline-api.js +32 -0
- package/dist/esm/modules/stream/core/sink-adapter.js +94 -24
- package/dist/esm/modules/stream/core/stream-helpers.js +160 -0
- package/dist/esm/modules/stream/core/utils.js +0 -23
- package/dist/esm/modules/stream/core/web-conversions.js +31 -0
- package/dist/esm/modules/stream/core/web-pipeline.js +90 -0
- package/dist/esm/modules/stream/index.base.js +2 -2
- package/dist/esm/modules/stream/index.browser.js +13 -19
- package/dist/esm/modules/stream/index.js +6 -10
- package/dist/esm/modules/stream/kernel/classes.js +618 -0
- package/dist/esm/modules/stream/kernel/compose-impl.js +225 -0
- package/dist/esm/modules/stream/kernel/destroy.js +255 -0
- package/dist/esm/modules/stream/kernel/duplexify.js +355 -0
- package/dist/esm/modules/stream/kernel/end-of-stream.js +212 -0
- package/dist/esm/modules/stream/kernel/errors.js +215 -0
- package/dist/esm/modules/stream/kernel/from.js +202 -0
- package/dist/esm/modules/stream/kernel/legacy.js +35 -0
- package/dist/esm/modules/stream/kernel/operators.js +285 -0
- package/dist/esm/modules/stream/kernel/pipeline-impl.js +561 -0
- package/dist/esm/modules/stream/kernel/readable-impl.js +963 -0
- package/dist/esm/modules/stream/kernel/state.js +253 -0
- package/dist/esm/modules/stream/kernel/stream-utils.js +212 -0
- package/dist/esm/modules/stream/kernel/web-adapters.js +584 -0
- package/dist/esm/modules/stream/kernel/writable.js +651 -0
- package/dist/esm/modules/stream/node/collector.js +7 -1
- package/dist/esm/modules/stream/node/compose.js +21 -206
- package/dist/esm/modules/stream/node/factories.js +34 -238
- package/dist/esm/modules/stream/node/pipeline.js +40 -94
- package/dist/esm/modules/stream/node/utils.js +15 -239
- package/dist/esm/modules/stream/node/web.js +27 -0
- package/dist/esm/modules/stream/pull-stream.js +15 -9
- package/dist/esm/modules/word/writer/streaming-writer.js +3 -0
- package/dist/iife/THIRD_PARTY_NOTICES.md +34 -2
- package/dist/iife/documonster.archive.iife.min.js +4 -4
- package/dist/iife/documonster.csv.iife.min.js +19 -19
- package/dist/iife/documonster.draw.iife.min.js +1 -1
- package/dist/iife/documonster.excel.iife.min.js +50 -50
- package/dist/iife/documonster.formula.iife.min.js +1 -1
- package/dist/iife/documonster.markdown.iife.min.js +1 -1
- package/dist/iife/documonster.mermaid.iife.min.js +1 -1
- package/dist/iife/documonster.pdf.iife.min.js +1 -1
- package/dist/iife/documonster.stream.iife.min.js +2 -2
- package/dist/iife/documonster.word.iife.min.js +60 -60
- package/dist/iife/documonster.xml.iife.min.js +1 -1
- package/dist/types/modules/excel/core/xlsx-io.browser.d.ts +5 -9
- package/dist/types/modules/excel/core/xlsx-io.d.ts +1 -3
- package/dist/types/modules/excel/core/xlsx-stream.d.ts +3 -2
- package/dist/types/modules/excel/stream/workbook-reader.browser.d.ts +2 -1
- package/dist/types/modules/excel/stream/workbook-reader.d.ts +2 -1
- package/dist/types/modules/stream/browser/collector.d.ts +1 -1
- package/dist/types/modules/stream/browser/compose.d.ts +14 -15
- package/dist/types/modules/stream/browser/duplex-pair.d.ts +10 -0
- package/dist/types/modules/stream/browser/factories.d.ts +22 -79
- package/dist/types/modules/stream/browser/pipeline.d.ts +8 -40
- package/dist/types/modules/stream/browser/utils.d.ts +6 -47
- package/dist/types/modules/stream/browser/web.d.ts +18 -0
- package/dist/types/modules/stream/core/add-abort-signal.d.ts +2 -1
- package/dist/types/modules/stream/core/consumers.d.ts +1 -1
- package/dist/types/modules/stream/core/event-utils.d.ts +2 -1
- package/dist/types/modules/stream/core/factories.d.ts +129 -0
- package/dist/types/modules/stream/core/pipeline-api.d.ts +10 -0
- package/dist/types/modules/stream/core/sink-adapter.d.ts +18 -1
- package/dist/types/modules/stream/core/stream-helpers.d.ts +76 -0
- package/dist/types/modules/stream/core/utils.d.ts +0 -9
- package/dist/types/modules/stream/core/web-conversions.d.ts +68 -0
- package/dist/types/modules/stream/core/web-pipeline.d.ts +27 -0
- package/dist/types/modules/stream/index.base.d.ts +6 -4
- package/dist/types/modules/stream/index.browser.d.ts +13 -14
- package/dist/types/modules/stream/index.d.ts +7 -10
- package/dist/types/modules/stream/kernel/classes.d.ts +220 -0
- package/dist/types/modules/stream/kernel/compose-impl.d.ts +10 -0
- package/dist/types/modules/stream/kernel/destroy.d.ts +21 -0
- package/dist/types/modules/stream/kernel/duplexify.d.ts +31 -0
- package/dist/types/modules/stream/kernel/end-of-stream.d.ts +28 -0
- package/dist/types/modules/stream/kernel/errors.d.ts +42 -0
- package/dist/types/modules/stream/kernel/from.d.ts +17 -0
- package/dist/types/modules/stream/kernel/legacy.d.ts +20 -0
- package/dist/types/modules/stream/kernel/operators.d.ts +44 -0
- package/dist/types/modules/stream/kernel/pipeline-impl.d.ts +32 -0
- package/dist/types/modules/stream/kernel/readable-impl.d.ts +34 -0
- package/dist/types/modules/stream/kernel/state.d.ts +223 -0
- package/dist/types/modules/stream/kernel/stream-utils.d.ts +34 -0
- package/dist/types/modules/stream/kernel/web-adapters.d.ts +65 -0
- package/dist/types/modules/stream/kernel/writable.d.ts +107 -0
- package/dist/types/modules/stream/node/collector.d.ts +7 -2
- package/dist/types/modules/stream/node/compose.d.ts +31 -5
- package/dist/types/modules/stream/node/factories.d.ts +22 -85
- package/dist/types/modules/stream/node/pipeline.d.ts +4 -15
- package/dist/types/modules/stream/node/utils.d.ts +9 -49
- package/dist/types/modules/stream/node/web.d.ts +18 -0
- package/dist/types/modules/stream/pull-stream.d.ts +2 -2
- package/dist/types/modules/stream/types.d.ts +24 -6
- package/package.json +5 -3
- package/dist/esm/modules/stream/browser/_lazy.js +0 -30
- package/dist/esm/modules/stream/browser/chunk-buffer.js +0 -223
- package/dist/esm/modules/stream/browser/duplex.js +0 -979
- package/dist/esm/modules/stream/browser/helpers.js +0 -44
- package/dist/esm/modules/stream/browser/microtask-context.js +0 -60
- package/dist/esm/modules/stream/browser/passthrough.js +0 -16
- package/dist/esm/modules/stream/browser/pipe-manager.js +0 -161
- package/dist/esm/modules/stream/browser/readable.js +0 -2208
- package/dist/esm/modules/stream/browser/transform.js +0 -1241
- package/dist/esm/modules/stream/browser/writable.js +0 -1316
- package/dist/esm/modules/stream/core/end-args.js +0 -24
- package/dist/esm/modules/stream/node/writable.js +0 -125
- package/dist/esm/modules/stream/utils.base.js +0 -126
- package/dist/esm/modules/stream/utils.browser.js +0 -80
- package/dist/esm/modules/stream/utils.js +0 -81
- package/dist/types/modules/stream/browser/_lazy.d.ts +0 -20
- package/dist/types/modules/stream/browser/chunk-buffer.d.ts +0 -61
- package/dist/types/modules/stream/browser/duplex.d.ts +0 -299
- package/dist/types/modules/stream/browser/helpers.d.ts +0 -13
- package/dist/types/modules/stream/browser/microtask-context.d.ts +0 -44
- package/dist/types/modules/stream/browser/passthrough.d.ts +0 -11
- package/dist/types/modules/stream/browser/pipe-manager.d.ts +0 -38
- package/dist/types/modules/stream/browser/readable.d.ts +0 -379
- package/dist/types/modules/stream/browser/transform.d.ts +0 -314
- package/dist/types/modules/stream/browser/writable.d.ts +0 -238
- package/dist/types/modules/stream/core/end-args.d.ts +0 -20
- package/dist/types/modules/stream/node/writable.d.ts +0 -53
- package/dist/types/modules/stream/utils.base.d.ts +0 -63
- package/dist/types/modules/stream/utils.browser.d.ts +0 -23
- package/dist/types/modules/stream/utils.d.ts +0 -24
package/README.md
CHANGED
|
@@ -211,7 +211,7 @@ const buffer = await Workbook.toBuffer(wb);
|
|
|
211
211
|
|
|
212
212
|
```html
|
|
213
213
|
<!-- Script tag (no bundler) — one IIFE per module, each under the shared `Documonster` global -->
|
|
214
|
-
<script src="https://unpkg.com/documonster@0.
|
|
214
|
+
<script src="https://unpkg.com/documonster@0.21.0/dist/iife/documonster.excel.iife.min.js"></script>
|
|
215
215
|
<script>
|
|
216
216
|
const { Workbook, Cell } = Documonster.Excel;
|
|
217
217
|
const wb = Workbook.create();
|
package/README_zh.md
CHANGED
|
@@ -198,7 +198,7 @@ const buffer = await Workbook.toBuffer(wb);
|
|
|
198
198
|
|
|
199
199
|
```html
|
|
200
200
|
<!-- Script 标签(无需打包工具)— 每个模块一个 IIFE,共享同一个 `Documonster` 全局 -->
|
|
201
|
-
<script src="https://unpkg.com/documonster@0.
|
|
201
|
+
<script src="https://unpkg.com/documonster@0.21.0/dist/iife/documonster.excel.iife.min.js"></script>
|
|
202
202
|
<script>
|
|
203
203
|
const { Workbook, Cell } = Documonster.Excel;
|
|
204
204
|
const wb = Workbook.create();
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -5,8 +5,8 @@ documonster as a whole is licensed under the Apache License, Version 2.0
|
|
|
5
5
|
third-party components identified, and do not affect the licensing of the
|
|
6
6
|
remainder of the project.
|
|
7
7
|
|
|
8
|
-
The PDF, Word, Formula, CSV, Markdown, XML,
|
|
9
|
-
|
|
8
|
+
The PDF, Word, Formula, CSV, Markdown, XML, and Archive modules, as well as the
|
|
9
|
+
parts of the Excel and Stream modules not listed below, are original works of
|
|
10
10
|
the documonster authors and are licensed under the Apache License, Version 2.0.
|
|
11
11
|
|
|
12
12
|
---
|
|
@@ -39,3 +39,35 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
39
39
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
40
40
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
41
41
|
SOFTWARE.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Node.js (Stream module kernel)
|
|
46
|
+
|
|
47
|
+
The stream kernel in `src/modules/stream/kernel/` — the browser's stream classes,
|
|
48
|
+
and the Web adapters and pipeline both entry points use — is a port of Node.js
|
|
49
|
+
`lib/internal/streams/` and `lib/internal/webstreams/adapters.js`. That
|
|
50
|
+
derivative code remains subject to the Node.js MIT License, reproduced below.
|
|
51
|
+
The following copyright notice and permission notice are retained in
|
|
52
|
+
compliance with that license.
|
|
53
|
+
|
|
54
|
+
Copyright Joyent, Inc. and other Node contributors.
|
|
55
|
+
|
|
56
|
+
Permission is hereby granted, free of charge, to any person obtaining a
|
|
57
|
+
copy of this software and associated documentation files (the
|
|
58
|
+
"Software"), to deal in the Software without restriction, including
|
|
59
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
60
|
+
distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
61
|
+
persons to whom the Software is furnished to do so, subject to the
|
|
62
|
+
following conditions:
|
|
63
|
+
|
|
64
|
+
The above copyright notice and this permission notice shall be included
|
|
65
|
+
in all copies or substantial portions of the Software.
|
|
66
|
+
|
|
67
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
68
|
+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
69
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
|
70
|
+
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
71
|
+
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
72
|
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
73
|
+
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -21,7 +21,8 @@ export class ByteQueue {
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
_dropHeadChunk() {
|
|
24
|
-
|
|
24
|
+
// Release the consumed chunk now rather than at the next compaction.
|
|
25
|
+
this._chunks[this._chunkHead++] = undefined;
|
|
25
26
|
this._headOffset = 0;
|
|
26
27
|
this._compactConsumedChunks();
|
|
27
28
|
}
|
|
@@ -314,6 +314,8 @@ onmessage = async (ev) => {
|
|
|
314
314
|
class WorkerInflateRaw extends Duplex {
|
|
315
315
|
constructor(workerUrl) {
|
|
316
316
|
super({
|
|
317
|
+
// Output is pushed as the worker replies, not on demand.
|
|
318
|
+
read() { },
|
|
317
319
|
write: (chunk, _encoding, callback) => {
|
|
318
320
|
this._doWrite(chunk, callback);
|
|
319
321
|
},
|
|
@@ -465,6 +467,8 @@ class FallbackInflateRaw extends Duplex {
|
|
|
465
467
|
this.chunks = [];
|
|
466
468
|
this._finished = false;
|
|
467
469
|
}
|
|
470
|
+
// Output is pushed when decompression completes, not on demand.
|
|
471
|
+
_read() { }
|
|
468
472
|
_decompress(callback) {
|
|
469
473
|
try {
|
|
470
474
|
// Combine all chunks
|
|
@@ -513,6 +517,8 @@ export function createParseClass(createInflateRawFn) {
|
|
|
513
517
|
constructor(opts = {}) {
|
|
514
518
|
super({
|
|
515
519
|
objectMode: true,
|
|
520
|
+
// Entries are pushed by the parse loop, not on demand.
|
|
521
|
+
read() { },
|
|
516
522
|
write: (chunk, _encoding, callback) => {
|
|
517
523
|
this._handleWrite(chunk, callback);
|
|
518
524
|
},
|
|
@@ -21,7 +21,7 @@ import { getUtf8ByteLength } from "../constants.js";
|
|
|
21
21
|
import { CsvError } from "../errors.js";
|
|
22
22
|
import { parseCsv } from "./sync.js";
|
|
23
23
|
import { CsvParserStream } from "../stream/parser.js";
|
|
24
|
-
import { isReadableStreamLike, readableStreamToAsyncIterable } from "../../stream/
|
|
24
|
+
import { isReadableStreamLike, readableStreamToAsyncIterable } from "../../stream/core/stream-helpers.js";
|
|
25
25
|
import { toError } from "../../../utils/errors.js";
|
|
26
26
|
/**
|
|
27
27
|
* Rows buffered for the generator's consumer before the parser is paused.
|
|
@@ -40,6 +40,7 @@ function normalizeAsyncInput(input) {
|
|
|
40
40
|
return input;
|
|
41
41
|
}
|
|
42
42
|
if (isReadableStreamLike(input)) {
|
|
43
|
+
// The documented input: a Web stream of text or bytes, like any other source here.
|
|
43
44
|
return readableStreamToAsyncIterable(input);
|
|
44
45
|
}
|
|
45
46
|
throw new TypeError("input must be an AsyncIterable or a ReadableStream");
|
|
@@ -24,7 +24,7 @@ import { addWorksheet, getWorksheet } from "#platform/modules/excel/core/workboo
|
|
|
24
24
|
import { addRow, eachRow } from "../core/worksheet.js";
|
|
25
25
|
import { ExcelDownloadError } from "../errors.js";
|
|
26
26
|
import { pipeline } from "#platform/modules/stream/index";
|
|
27
|
-
import { readableStreamToAsyncIterable } from "../../stream/
|
|
27
|
+
import { readableStreamToAsyncIterable } from "../../stream/core/stream-helpers.js";
|
|
28
28
|
import { createDateParser, createDateFormatter, createIsoDateFormatter } from "../../../utils/datetime.js";
|
|
29
29
|
// =============================================================================
|
|
30
30
|
// Constants
|
|
@@ -142,15 +142,11 @@ export async function writeStream(wb, stream, options) {
|
|
|
142
142
|
*
|
|
143
143
|
* ### Leaving `for await` early
|
|
144
144
|
*
|
|
145
|
-
*
|
|
146
|
-
* `
|
|
147
|
-
* `
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
* Note this differs from the Node build, where Node's own async iterator destroys
|
|
151
|
-
* the stream with an `AbortError` on early exit and sets `errored`. `errored` is
|
|
152
|
-
* therefore not a portable way to distinguish "the consumer stopped" from
|
|
153
|
-
* "serialization failed"; track that yourself if the same code runs on both.
|
|
145
|
+
* As in Node, breaking out of the loop destroys the stream with an `AbortError`:
|
|
146
|
+
* `errored` is set and an attached `'error'` listener receives it. To leave
|
|
147
|
+
* silently, `destroy()` the stream before `break`, or iterate
|
|
148
|
+
* `stream.iterator({ destroyOnReturn: false })` and `destroy()` it yourself
|
|
149
|
+
* afterwards — otherwise the serializer stays parked.
|
|
154
150
|
*/
|
|
155
151
|
export function toStream(wb, options) {
|
|
156
152
|
if (options?.format === "xlsb") {
|
|
@@ -218,9 +218,7 @@ export async function writeStream(wb, stream, options) {
|
|
|
218
218
|
* }
|
|
219
219
|
* ```
|
|
220
220
|
*
|
|
221
|
-
* The browser build
|
|
222
|
-
* a portable way to tell "the consumer stopped" from "serialization failed" —
|
|
223
|
-
* track that yourself if the same code runs on both platforms.
|
|
221
|
+
* The browser build behaves the same way: its stream kernel is a port of Node's.
|
|
224
222
|
*/
|
|
225
223
|
export function toStream(wb, options) {
|
|
226
224
|
if (options?.format === "xlsb") {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { createReadable } from "#platform/modules/stream/index";
|
|
2
2
|
const textEncoder = new TextEncoder();
|
|
3
|
+
/** 64 KB: the same threshold on Node (whose own default is 16 KB on Windows), Bun and browsers. */
|
|
4
|
+
const DEFAULT_HIGH_WATER_MARK = 64 * 1024;
|
|
3
5
|
/**
|
|
4
6
|
* Wrap a push-shaped XLSX serializer in a demand-driven readable byte stream.
|
|
5
7
|
*
|
|
@@ -8,7 +10,7 @@ const textEncoder = new TextEncoder();
|
|
|
8
10
|
* the backpressure signal the writer parks on at its next ZIP-entry boundary.
|
|
9
11
|
*/
|
|
10
12
|
export function createXlsxByteStream(serialize, options) {
|
|
11
|
-
const { highWaterMark, ...writeOptions } = options ?? {};
|
|
13
|
+
const { highWaterMark = DEFAULT_HIGH_WATER_MARK, ...writeOptions } = options ?? {};
|
|
12
14
|
const listeners = new Map();
|
|
13
15
|
let started = false;
|
|
14
16
|
let ended = false;
|
|
@@ -222,13 +222,11 @@ export class WorkbookReaderBase extends EventEmitter {
|
|
|
222
222
|
}
|
|
223
223
|
throw new ExcelFileError(String(input), "read", "Could not recognise input");
|
|
224
224
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
231
|
-
}
|
|
225
|
+
/** Release every stored worksheet that provides a `cleanup`, and wait until each is gone. */
|
|
226
|
+
async _cleanupWaitingWorksheets(waitingWorksheets) {
|
|
227
|
+
await Promise.all(waitingWorksheets.map(async (ws) => {
|
|
228
|
+
await ws?.cleanup?.();
|
|
229
|
+
}));
|
|
232
230
|
}
|
|
233
231
|
// Unified implementations using passed-in classes
|
|
234
232
|
_createWorksheetReader(params) {
|
|
@@ -302,9 +300,11 @@ export class WorkbookReaderBase extends EventEmitter {
|
|
|
302
300
|
}
|
|
303
301
|
yield* this._processWaitingWorksheets(waitingWorksheets);
|
|
304
302
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
303
|
+
finally {
|
|
304
|
+
// On every way out — the end, an error, or a consumer leaving early,
|
|
305
|
+
// which returns the generator and runs no `catch` — so a sheet stored
|
|
306
|
+
// but never replayed does not leave its spool behind.
|
|
307
|
+
await this._cleanupWaitingWorksheets(waitingWorksheets);
|
|
308
308
|
}
|
|
309
309
|
}
|
|
310
310
|
_emitEntry(payload) {
|
|
@@ -26,12 +26,13 @@ class WorkbookReader extends WorkbookReaderBase {
|
|
|
26
26
|
// a leftover temporary file is self-describing rather than a lie.
|
|
27
27
|
const isXlsb = /\.bin$/i.test(entry.path);
|
|
28
28
|
const filePath = join(tmpDir, `sheet${sheetNo}.${isXlsb ? "bin" : "xml"}`);
|
|
29
|
-
const cleanup = () => {
|
|
30
|
-
remove(tmpDir).catch(() => { });
|
|
31
|
-
};
|
|
32
29
|
const maxBytes = this._maxBufferedBytes;
|
|
30
|
+
const tempStream = createWriteStream(filePath);
|
|
31
|
+
// The file goes only once nothing has it open: removing a directory with
|
|
32
|
+
// an open file fails on Windows and leaves it behind.
|
|
33
|
+
let removed;
|
|
34
|
+
const cleanup = () => (removed ?? (removed = releaseFile(tempStream).then(() => remove(tmpDir))));
|
|
33
35
|
const writePromise = new Promise((resolve, reject) => {
|
|
34
|
-
const tempStream = createWriteStream(filePath);
|
|
35
36
|
tempStream.on("error", reject);
|
|
36
37
|
tempStream.on("finish", resolve);
|
|
37
38
|
// Track bytes written to detect oversized waiting worksheets.
|
|
@@ -65,6 +66,10 @@ class WorkbookReader extends WorkbookReaderBase {
|
|
|
65
66
|
});
|
|
66
67
|
entry.pipe(tempStream);
|
|
67
68
|
});
|
|
69
|
+
// Awaited when the sheet is replayed; a sheet never replayed (the reader
|
|
70
|
+
// left early, or failed first) must not turn its failure into an
|
|
71
|
+
// unhandled rejection.
|
|
72
|
+
writePromise.catch(() => { });
|
|
68
73
|
return { sheetNo, path: filePath, cleanup, writePromise, isXlsb };
|
|
69
74
|
}
|
|
70
75
|
async *_processWaitingWorksheets(waitingWorksheets) {
|
|
@@ -80,10 +85,29 @@ class WorkbookReader extends WorkbookReaderBase {
|
|
|
80
85
|
finally {
|
|
81
86
|
// The file goes away now, so a reader not yet read must say so if it is read later rather than wait forever.
|
|
82
87
|
part.finish();
|
|
83
|
-
|
|
84
|
-
|
|
88
|
+
// Remove the file only once the stream has let go of it. The open is
|
|
89
|
+
// asynchronous and may still be pending when a consumer stops early;
|
|
90
|
+
// removing first fails it with ENOENT on a stream nobody listens to
|
|
91
|
+
// any more (every time on Bun, by luck not on Node).
|
|
92
|
+
await releaseFile(fileStream);
|
|
93
|
+
await ws.cleanup();
|
|
85
94
|
}
|
|
86
95
|
}
|
|
87
96
|
}
|
|
88
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Destroy a file stream and wait until its descriptor is closed. Anything it
|
|
100
|
+
* reports from here on concerns a read or write that was abandoned, so it is
|
|
101
|
+
* dropped.
|
|
102
|
+
*/
|
|
103
|
+
function releaseFile(stream) {
|
|
104
|
+
if (stream.closed) {
|
|
105
|
+
return Promise.resolve();
|
|
106
|
+
}
|
|
107
|
+
return new Promise(resolve => {
|
|
108
|
+
stream.on("error", () => { });
|
|
109
|
+
stream.once("close", () => resolve());
|
|
110
|
+
stream.destroy();
|
|
111
|
+
});
|
|
112
|
+
}
|
|
89
113
|
export { WorkbookReader, WorkbookReaderOptionsSchema };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Browser Stream - Collector
|
|
3
3
|
*/
|
|
4
|
-
import { Writable } from "./writable.js";
|
|
5
4
|
import { toBinaryChunk } from "../core/binary-chunk.js";
|
|
6
5
|
import { StreamTypeError } from "../errors.js";
|
|
6
|
+
import { Writable } from "../kernel/writable.js";
|
|
7
7
|
import { concatUint8Arrays, chunksToString } from "../../../utils/binary.js";
|
|
8
8
|
// =============================================================================
|
|
9
9
|
// Collector Stream
|
|
@@ -13,10 +13,8 @@ import { concatUint8Arrays, chunksToString } from "../../../utils/binary.js";
|
|
|
13
13
|
*/
|
|
14
14
|
export class Collector extends Writable {
|
|
15
15
|
constructor(options) {
|
|
16
|
-
// The write handler collects into a closed-over array
|
|
17
|
-
// `this.chunks` after construction
|
|
18
|
-
// subclass `_write` method) keeps the synchronous direct-write path so
|
|
19
|
-
// chunks are collected before the caller's next synchronous statement.
|
|
16
|
+
// The write handler collects into a closed-over array exposed as
|
|
17
|
+
// `this.chunks` after construction, mirroring the Node Collector.
|
|
20
18
|
const chunks = [];
|
|
21
19
|
super({
|
|
22
20
|
...options,
|
|
@@ -1,233 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Browser Stream -
|
|
3
|
-
*
|
|
4
|
-
* Compose multiple transform streams into one.
|
|
5
|
-
* Aligned with Node.js compose semantics:
|
|
6
|
-
* - Backpressure: pauses `last` when composed buffer is full
|
|
7
|
-
* - Flush: waits for `last` to emit "end" before signalling completion
|
|
8
|
-
* - Error: destroys composed stream on any child error (not just emit)
|
|
9
|
-
*
|
|
10
|
-
* Uses Transform constructor options (write, final, destroy) instead of
|
|
11
|
-
* post-construction method overrides, keeping all internal Writable state
|
|
12
|
-
* (buffering, serialization, _writableLength, drain) properly tracked.
|
|
2
|
+
* Browser Stream - compose()
|
|
13
3
|
*/
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { getDefaultHighWaterMark } from "../core/utils.js";
|
|
17
|
-
// =============================================================================
|
|
18
|
-
// Compose
|
|
19
|
-
// =============================================================================
|
|
4
|
+
import { kernel } from "../kernel/classes.js";
|
|
5
|
+
import { composeImpl } from "../kernel/compose-impl.js";
|
|
20
6
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
7
|
+
* Combine stages into one Duplex: writes go into the first, reads come out of
|
|
8
|
+
* the last. Stages are what `pipeline` takes — streams, Web streams,
|
|
9
|
+
* generator functions, iterables — and the rules are Node's: no stage throws
|
|
10
|
+
* ERR_MISSING_ARGS, one stage is `Duplex.from(stage)`.
|
|
11
|
+
*
|
|
12
|
+
* `T` is what is written in and `R` what is read out. They are the caller's
|
|
13
|
+
* statement, like a type argument to `JSON.parse`: the stages are not checked
|
|
14
|
+
* against them, as Node's own `compose` checks nothing.
|
|
23
15
|
*/
|
|
24
|
-
export function compose(...
|
|
25
|
-
|
|
26
|
-
if (len === 0) {
|
|
27
|
-
// Boundary: an empty composition is a pure pass-through, so each input
|
|
28
|
-
// chunk (type T) is re-emitted unchanged as the declared output type R.
|
|
29
|
-
return new Transform({
|
|
30
|
-
objectMode: true,
|
|
31
|
-
transform: chunk => chunk
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
// Preserve identity: compose(single) returns the same transform.
|
|
35
|
-
if (len === 1) {
|
|
36
|
-
return transforms[0];
|
|
37
|
-
}
|
|
38
|
-
// Chain the transforms: first → second → ... → last
|
|
39
|
-
const first = transforms[0];
|
|
40
|
-
const last = transforms[len - 1];
|
|
41
|
-
// Pipe all transforms together
|
|
42
|
-
for (let i = 0; i < len - 1; i++) {
|
|
43
|
-
transforms[i].pipe(transforms[i + 1]);
|
|
44
|
-
}
|
|
45
|
-
// Track whether last is paused due to backpressure from composed.
|
|
46
|
-
let lastPaused = false;
|
|
47
|
-
// Track whether flush is handling the end sequence.
|
|
48
|
-
let flushing = false;
|
|
49
|
-
// Use per-side objectMode matching Node.js compose behavior.
|
|
50
|
-
// When the property is missing, default to false (same as Node.js Transform).
|
|
51
|
-
const readableObjMode = last.readableObjectMode ?? false;
|
|
52
|
-
const writableObjMode = first.writableObjectMode ?? false;
|
|
53
|
-
const registry = createListenerRegistry();
|
|
54
|
-
const composed = new Transform({
|
|
55
|
-
readableHighWaterMark: getDefaultHighWaterMark(readableObjMode),
|
|
56
|
-
writableHighWaterMark: getDefaultHighWaterMark(writableObjMode),
|
|
57
|
-
readableObjectMode: readableObjMode,
|
|
58
|
-
writableObjectMode: writableObjMode,
|
|
59
|
-
// Write path: forward writes into the head of the chain.
|
|
60
|
-
// Using the `write` constructor option ensures all writes go through the
|
|
61
|
-
// Transform's internal Writable state machine (_writableLength tracking,
|
|
62
|
-
// write serialization via _writeQueue, cork buffering, drain signals).
|
|
63
|
-
write(chunk, encoding, callback) {
|
|
64
|
-
try {
|
|
65
|
-
first.write(chunk, encoding, callback);
|
|
66
|
-
}
|
|
67
|
-
catch (err) {
|
|
68
|
-
callback(err);
|
|
69
|
-
}
|
|
70
|
-
},
|
|
71
|
-
// Flush path: end the head of the chain, then wait for `last` to emit
|
|
72
|
-
// "end" (readable exhaustion) before completing the composed stream.
|
|
73
|
-
// This is the `final` handler — called after all pending writes complete,
|
|
74
|
-
// before "finish" is emitted. The Transform constructor wrapper will
|
|
75
|
-
// automatically push(null) on the readable side after our callback.
|
|
76
|
-
final(callback) {
|
|
77
|
-
flushing = true;
|
|
78
|
-
// If `last` already ended independently (e.g. a "take N" transform that
|
|
79
|
-
// pushed null on its own), complete immediately — the "end" event has
|
|
80
|
-
// already fired and a new once("end") listener would never trigger.
|
|
81
|
-
if (last.readableEnded) {
|
|
82
|
-
callback();
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
const onEnd = () => {
|
|
86
|
-
cleanupFlush();
|
|
87
|
-
callback();
|
|
88
|
-
};
|
|
89
|
-
const onError = (err) => {
|
|
90
|
-
cleanupFlush();
|
|
91
|
-
callback(err);
|
|
92
|
-
};
|
|
93
|
-
const cleanupFlush = () => {
|
|
94
|
-
last.off?.("end", onEnd);
|
|
95
|
-
last.off?.("error", onError);
|
|
96
|
-
};
|
|
97
|
-
last.once?.("end", onEnd);
|
|
98
|
-
last.once?.("error", onError);
|
|
99
|
-
first.end();
|
|
100
|
-
},
|
|
101
|
-
// Destroy path: clean up all listeners and destroy all child transforms.
|
|
102
|
-
destroy(_error, callback) {
|
|
103
|
-
try {
|
|
104
|
-
registry.cleanup();
|
|
105
|
-
for (const t of transforms) {
|
|
106
|
-
t.destroy(_error ?? undefined);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
finally {
|
|
110
|
-
callback(_error);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
// Hook into the internal _readable's _read method so that when the
|
|
115
|
-
// PipeManager (or any consumer) pulls data, we resume `last` if it was
|
|
116
|
-
// paused due to backpressure. This is the browser equivalent of Node.js
|
|
117
|
-
// compose's `read()` option which is called by the native pipe mechanism.
|
|
118
|
-
//
|
|
119
|
-
// Compose the new behavior with the Transform's existing _read patch
|
|
120
|
-
// (which releases _afterTransformCallback). Although _afterTransformCallback
|
|
121
|
-
// is not used when the `write` option is provided, preserving it is a
|
|
122
|
-
// defensive measure against future changes.
|
|
123
|
-
const composedReadable = composed._readable;
|
|
124
|
-
const origRead = composedReadable._read;
|
|
125
|
-
composedReadable._read = () => {
|
|
126
|
-
origRead?.call(composedReadable);
|
|
127
|
-
if (lastPaused) {
|
|
128
|
-
lastPaused = false;
|
|
129
|
-
last.resume?.();
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
// Forward errors from all transforms — destroy composed on error (matches Node.js).
|
|
133
|
-
for (const t of transforms) {
|
|
134
|
-
registry.add(t, "error", (err) => composed.destroy(err));
|
|
135
|
-
}
|
|
136
|
-
// Drain is handled by the internal Writable's own drain → Transform's
|
|
137
|
-
// _setupSideForwarding → composed.emit("drain"). No need to forward from
|
|
138
|
-
// `first` — that would cause double-drain when both buffers cross below HWM.
|
|
139
|
-
// Track whether both sides have completed so we can auto-destroy the composed
|
|
140
|
-
// stream (emitting 'close'), matching Node.js compose behavior.
|
|
141
|
-
let composedEndFired = false;
|
|
142
|
-
let composedFinishFired = false;
|
|
143
|
-
const maybeAutoDestroy = () => {
|
|
144
|
-
if (composedEndFired && composedFinishFired && !composed.destroyed) {
|
|
145
|
-
composed.destroy();
|
|
146
|
-
}
|
|
147
|
-
};
|
|
148
|
-
// Finish is handled by the internal Writable's own lifecycle: when the
|
|
149
|
-
// `final` handler calls callback(), the internal Writable emits "finish",
|
|
150
|
-
// which _setupSideForwarding propagates to composed.emit("finish").
|
|
151
|
-
// Forwarding finish from `last` would cause double-finish.
|
|
152
|
-
// Track the composed stream's own finish for auto-destroy.
|
|
153
|
-
composed.once("finish", () => {
|
|
154
|
-
composedFinishFired = true;
|
|
155
|
-
maybeAutoDestroy();
|
|
156
|
-
});
|
|
157
|
-
// Eagerly attach data/end forwarding from `last` to composed (matching Node.js).
|
|
158
|
-
// Node.js compose immediately attaches last.on("data") so data flows into
|
|
159
|
-
// composed's buffer right away, ensuring no data is missed.
|
|
160
|
-
registry.add(last, "data", (chunk) => {
|
|
161
|
-
if (!composed.push(chunk)) {
|
|
162
|
-
lastPaused = true;
|
|
163
|
-
last.pause?.();
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
registry.once(last, "end", () => {
|
|
167
|
-
// When flushing, the final handler + Transform wrapper handles stream
|
|
168
|
-
// termination (push(null)). Otherwise (e.g. last ended independently),
|
|
169
|
-
// we must push(null) ourselves.
|
|
170
|
-
if (!flushing) {
|
|
171
|
-
composed.push(null);
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
// Track when the composed stream's own 'end' fires (from push(null)).
|
|
175
|
-
composed.once("end", () => {
|
|
176
|
-
composedEndFired = true;
|
|
177
|
-
maybeAutoDestroy();
|
|
178
|
-
});
|
|
179
|
-
// Delegate cork/uncork to the head of the chain.
|
|
180
|
-
composed.cork = () => {
|
|
181
|
-
first.cork?.();
|
|
182
|
-
};
|
|
183
|
-
composed.uncork = () => {
|
|
184
|
-
first.uncork?.();
|
|
185
|
-
};
|
|
186
|
-
// Safety net: ensure listener cleanup even if close fires through an
|
|
187
|
-
// unexpected path (destroy option already calls registry.cleanup).
|
|
188
|
-
composed.once("close", () => {
|
|
189
|
-
registry.cleanup();
|
|
190
|
-
});
|
|
191
|
-
// Reflect underlying readability/writability like the previous duck-typed wrapper
|
|
192
|
-
Object.defineProperty(composed, "readable", {
|
|
193
|
-
get: () => last.readable
|
|
194
|
-
});
|
|
195
|
-
Object.defineProperty(composed, "writable", {
|
|
196
|
-
get: () => first.writable
|
|
197
|
-
});
|
|
198
|
-
// Proxy writable-side state to `first` so properties like writableEnded and
|
|
199
|
-
// writableFinished reflect the actual head-of-chain state, not the inner
|
|
200
|
-
// Transform wrapper which is never written to directly.
|
|
201
|
-
Object.defineProperty(composed, "writableEnded", {
|
|
202
|
-
get: () => first.writableEnded ?? false
|
|
203
|
-
});
|
|
204
|
-
Object.defineProperty(composed, "writableFinished", {
|
|
205
|
-
get: () => first.writableFinished ?? false
|
|
206
|
-
});
|
|
207
|
-
Object.defineProperty(composed, "writableLength", {
|
|
208
|
-
get: () => first.writableLength ?? 0
|
|
209
|
-
});
|
|
210
|
-
Object.defineProperty(composed, "writableHighWaterMark", {
|
|
211
|
-
get: () => first.writableHighWaterMark ?? getDefaultHighWaterMark(false)
|
|
212
|
-
});
|
|
213
|
-
Object.defineProperty(composed, "writableCorked", {
|
|
214
|
-
get: () => first.writableCorked ?? 0
|
|
215
|
-
});
|
|
216
|
-
Object.defineProperty(composed, "writableNeedDrain", {
|
|
217
|
-
get: () => first.writableNeedDrain ?? false
|
|
218
|
-
});
|
|
219
|
-
// Proxy readable-side state to `last`.
|
|
220
|
-
Object.defineProperty(composed, "readableEnded", {
|
|
221
|
-
get: () => last.readableEnded ?? false
|
|
222
|
-
});
|
|
223
|
-
Object.defineProperty(composed, "readableLength", {
|
|
224
|
-
get: () => last.readableLength ?? 0
|
|
225
|
-
});
|
|
226
|
-
Object.defineProperty(composed, "readableHighWaterMark", {
|
|
227
|
-
get: () => last.readableHighWaterMark ?? getDefaultHighWaterMark(false)
|
|
228
|
-
});
|
|
229
|
-
Object.defineProperty(composed, "readableFlowing", {
|
|
230
|
-
get: () => last.readableFlowing ?? null
|
|
231
|
-
});
|
|
232
|
-
return composed;
|
|
16
|
+
export function compose(...stages) {
|
|
17
|
+
return composeImpl(kernel, ...stages);
|
|
233
18
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser Stream - duplexPair()
|
|
3
|
+
*
|
|
4
|
+
* Its own module: `DuplexSide extends Duplex` is a top-level class, which a
|
|
5
|
+
* bundler keeps — with the whole Duplex chain behind it — whenever the module
|
|
6
|
+
* holding it is kept.
|
|
7
|
+
*/
|
|
8
|
+
import { Duplex } from "../kernel/classes.js";
|
|
9
|
+
import { nextTick } from "../kernel/state.js";
|
|
10
|
+
/**
|
|
11
|
+
* One end of a {@link duplexPair}: what is written here is pushed to the
|
|
12
|
+
* other end, and the write completes only when the other end reads it.
|
|
13
|
+
* Port of Node.js `lib/internal/streams/duplexpair.js`.
|
|
14
|
+
*/
|
|
15
|
+
class DuplexSide extends Duplex {
|
|
16
|
+
constructor() {
|
|
17
|
+
super(...arguments);
|
|
18
|
+
this._otherSide = null;
|
|
19
|
+
this._pendingCallback = null;
|
|
20
|
+
}
|
|
21
|
+
_initOtherSide(otherSide) {
|
|
22
|
+
this._otherSide = otherSide;
|
|
23
|
+
}
|
|
24
|
+
_read(_size) {
|
|
25
|
+
// The other side's write waits for this read; release it.
|
|
26
|
+
const callback = this._pendingCallback;
|
|
27
|
+
if (callback) {
|
|
28
|
+
this._pendingCallback = null;
|
|
29
|
+
callback();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
_write(chunk, _encoding, callback) {
|
|
33
|
+
const other = this._otherSide;
|
|
34
|
+
if (chunk.length === 0) {
|
|
35
|
+
nextTick(callback);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
other.push(chunk);
|
|
39
|
+
other._pendingCallback = callback;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
_final(callback) {
|
|
43
|
+
this._otherSide.on("end", callback);
|
|
44
|
+
this._otherSide.push(null);
|
|
45
|
+
}
|
|
46
|
+
_destroy(err, callback) {
|
|
47
|
+
const other = this._otherSide;
|
|
48
|
+
if (other !== null && !other.destroyed) {
|
|
49
|
+
nextTick(() => {
|
|
50
|
+
if (other.destroyed) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (err) {
|
|
54
|
+
other.destroy();
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
other.push(null);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
callback(err);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Two connected Duplex streams: each one reads what the other writes. */
|
|
65
|
+
export function duplexPair(options) {
|
|
66
|
+
const side0 = new DuplexSide(options);
|
|
67
|
+
const side1 = new DuplexSide(options);
|
|
68
|
+
side0._initOtherSide(side1);
|
|
69
|
+
side1._initOtherSide(side0);
|
|
70
|
+
return [side0, side1];
|
|
71
|
+
}
|