@cj-tech-master/excelts 4.2.0 → 4.2.1-canary.20260111102127.f808a37
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/THIRD_PARTY_NOTICES.md +0 -31
- package/dist/browser/index.browser.d.ts +1 -0
- package/dist/browser/index.browser.js +12 -0
- package/dist/{types/modules/archive → browser/modules/archive/compression}/compress.base.d.ts +1 -0
- package/dist/browser/modules/archive/{compress.base.js → compression/compress.base.js} +2 -1
- package/dist/{types/modules/archive → browser/modules/archive/compression}/compress.browser.d.ts +10 -8
- package/dist/{esm/modules/archive → browser/modules/archive/compression}/compress.browser.js +18 -19
- package/dist/browser/modules/archive/{compress.d.ts → compression/compress.d.ts} +2 -2
- package/dist/browser/modules/archive/{compress.js → compression/compress.js} +1 -1
- package/dist/browser/modules/archive/{crc32.browser.d.ts → compression/crc32.browser.d.ts} +1 -1
- package/dist/browser/modules/archive/{crc32.d.ts → compression/crc32.d.ts} +1 -1
- package/dist/browser/modules/archive/{crc32.js → compression/crc32.js} +1 -1
- package/dist/browser/modules/archive/{deflate-fallback.js → compression/deflate-fallback.js} +1 -1
- package/dist/browser/modules/archive/{streaming-compress.browser.d.ts → compression/streaming-compress.browser.d.ts} +2 -2
- package/dist/browser/modules/archive/{streaming-compress.browser.js → compression/streaming-compress.browser.js} +3 -3
- package/dist/browser/modules/archive/{streaming-compress.d.ts → compression/streaming-compress.d.ts} +2 -2
- package/dist/browser/modules/archive/{streaming-compress.js → compression/streaming-compress.js} +2 -2
- package/dist/browser/modules/archive/defaults.d.ts +1 -0
- package/dist/browser/modules/archive/defaults.js +6 -3
- package/dist/browser/modules/archive/index.base.d.ts +4 -4
- package/dist/browser/modules/archive/index.base.js +3 -6
- package/dist/browser/modules/archive/index.browser.d.ts +3 -4
- package/dist/browser/modules/archive/index.browser.js +3 -7
- package/dist/browser/modules/archive/index.d.ts +3 -4
- package/dist/browser/modules/archive/index.js +3 -5
- package/dist/browser/modules/archive/internal/byte-queue.d.ts +33 -0
- package/dist/browser/modules/archive/internal/byte-queue.js +407 -0
- package/dist/browser/modules/archive/io/archive-sink.d.ts +9 -0
- package/dist/browser/modules/archive/io/archive-sink.js +77 -0
- package/dist/browser/modules/archive/io/archive-source.d.ts +8 -0
- package/dist/browser/modules/archive/io/archive-source.js +107 -0
- package/dist/browser/modules/archive/{extract.d.ts → unzip/extract.d.ts} +2 -2
- package/dist/browser/modules/archive/unzip/index.d.ts +40 -0
- package/dist/browser/modules/archive/unzip/index.js +164 -0
- package/dist/browser/modules/archive/{parse.base.d.ts → unzip/stream.base.d.ts} +58 -3
- package/dist/browser/modules/archive/unzip/stream.base.js +1022 -0
- package/dist/browser/modules/archive/{parse.browser.d.ts → unzip/stream.browser.d.ts} +1 -1
- package/dist/browser/modules/archive/{parse.browser.js → unzip/stream.browser.js} +376 -110
- package/dist/browser/modules/archive/{parse.d.ts → unzip/stream.d.ts} +2 -2
- package/dist/{esm/modules/archive/parse.js → browser/modules/archive/unzip/stream.js} +7 -6
- package/dist/{types/modules/archive → browser/modules/archive/unzip}/zip-parser.d.ts +1 -1
- package/dist/{esm/modules/archive → browser/modules/archive/unzip}/zip-parser.js +38 -24
- package/dist/browser/modules/archive/utils/async-queue.d.ts +7 -0
- package/dist/browser/modules/archive/utils/async-queue.js +103 -0
- package/dist/browser/modules/archive/utils/bytes.js +16 -16
- package/dist/browser/modules/archive/utils/compressibility.d.ts +10 -0
- package/dist/browser/modules/archive/utils/compressibility.js +57 -0
- package/dist/browser/modules/archive/utils/parse-buffer.js +21 -23
- package/dist/browser/modules/archive/utils/pattern-scanner.d.ts +21 -0
- package/dist/browser/modules/archive/utils/pattern-scanner.js +27 -0
- package/dist/browser/modules/archive/utils/timestamps.js +62 -1
- package/dist/browser/modules/archive/utils/zip-extra-fields.d.ts +1 -1
- package/dist/browser/modules/archive/utils/zip-extra-fields.js +26 -14
- package/dist/browser/modules/archive/zip/index.d.ts +42 -0
- package/dist/browser/modules/archive/zip/index.js +157 -0
- package/dist/browser/modules/archive/{streaming-zip.d.ts → zip/stream.d.ts} +28 -5
- package/dist/browser/modules/archive/{streaming-zip.js → zip/stream.js} +192 -48
- package/dist/browser/modules/archive/zip/zip-bytes.d.ts +73 -0
- package/dist/browser/modules/archive/zip/zip-bytes.js +239 -0
- package/dist/{esm/modules/archive → browser/modules/archive/zip}/zip-entry-metadata.js +3 -3
- package/dist/browser/modules/archive/{zip-records.d.ts → zip-spec/zip-records.d.ts} +20 -0
- package/dist/browser/modules/archive/zip-spec/zip-records.js +126 -0
- package/dist/browser/modules/excel/form-control.d.ts +2 -0
- package/dist/browser/modules/excel/form-control.js +54 -16
- package/dist/browser/modules/excel/stream/workbook-reader.browser.js +1 -1
- package/dist/browser/modules/excel/stream/workbook-writer.browser.d.ts +1 -1
- package/dist/browser/modules/excel/stream/workbook-writer.browser.js +1 -1
- package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +17 -3
- package/dist/browser/modules/excel/xlsx/xlsx.browser.js +3 -6
- package/dist/browser/modules/excel/xlsx/xlsx.js +1 -1
- package/dist/browser/modules/stream/streams.browser.d.ts +28 -28
- package/dist/browser/modules/stream/streams.browser.js +850 -697
- package/dist/browser/modules/stream/streams.js +140 -58
- package/dist/cjs/modules/archive/{compress.base.js → compression/compress.base.js} +2 -1
- package/dist/cjs/modules/archive/{compress.browser.js → compression/compress.browser.js} +18 -19
- package/dist/cjs/modules/archive/{compress.js → compression/compress.js} +1 -1
- package/dist/cjs/modules/archive/{crc32.js → compression/crc32.js} +1 -1
- package/dist/cjs/modules/archive/{deflate-fallback.js → compression/deflate-fallback.js} +1 -1
- package/dist/cjs/modules/archive/{streaming-compress.browser.js → compression/streaming-compress.browser.js} +3 -3
- package/dist/cjs/modules/archive/{streaming-compress.js → compression/streaming-compress.js} +2 -2
- package/dist/cjs/modules/archive/defaults.js +7 -4
- package/dist/cjs/modules/archive/index.base.js +9 -19
- package/dist/cjs/modules/archive/index.browser.js +4 -10
- package/dist/cjs/modules/archive/index.js +4 -8
- package/dist/cjs/modules/archive/internal/byte-queue.js +411 -0
- package/dist/cjs/modules/archive/io/archive-sink.js +82 -0
- package/dist/cjs/modules/archive/io/archive-source.js +114 -0
- package/dist/cjs/modules/archive/unzip/index.js +170 -0
- package/dist/cjs/modules/archive/unzip/stream.base.js +1044 -0
- package/dist/cjs/modules/archive/{parse.browser.js → unzip/stream.browser.js} +377 -111
- package/dist/cjs/modules/archive/{parse.js → unzip/stream.js} +9 -8
- package/dist/cjs/modules/archive/{zip-parser.js → unzip/zip-parser.js} +47 -33
- package/dist/cjs/modules/archive/utils/async-queue.js +106 -0
- package/dist/cjs/modules/archive/utils/bytes.js +16 -16
- package/dist/cjs/modules/archive/utils/compressibility.js +60 -0
- package/dist/cjs/modules/archive/utils/parse-buffer.js +21 -23
- package/dist/cjs/modules/archive/utils/pattern-scanner.js +31 -0
- package/dist/cjs/modules/archive/utils/timestamps.js +64 -3
- package/dist/cjs/modules/archive/utils/zip-extra-fields.js +26 -14
- package/dist/cjs/modules/archive/zip/index.js +162 -0
- package/dist/cjs/modules/archive/{streaming-zip.js → zip/stream.js} +194 -50
- package/dist/cjs/modules/archive/zip/zip-bytes.js +242 -0
- package/dist/cjs/modules/archive/{zip-entry-metadata.js → zip/zip-entry-metadata.js} +5 -5
- package/dist/cjs/modules/archive/zip-spec/zip-records.js +136 -0
- package/dist/cjs/modules/excel/form-control.js +54 -16
- package/dist/cjs/modules/excel/stream/workbook-reader.browser.js +2 -2
- package/dist/cjs/modules/excel/stream/workbook-writer.browser.js +4 -4
- package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +17 -3
- package/dist/cjs/modules/excel/xlsx/xlsx.browser.js +6 -9
- package/dist/cjs/modules/excel/xlsx/xlsx.js +2 -2
- package/dist/cjs/modules/stream/streams.browser.js +850 -697
- package/dist/cjs/modules/stream/streams.js +140 -58
- package/dist/esm/index.browser.js +12 -0
- package/dist/esm/modules/archive/{compress.base.js → compression/compress.base.js} +2 -1
- package/dist/{browser/modules/archive → esm/modules/archive/compression}/compress.browser.js +18 -19
- package/dist/esm/modules/archive/{compress.js → compression/compress.js} +1 -1
- package/dist/esm/modules/archive/{crc32.js → compression/crc32.js} +1 -1
- package/dist/esm/modules/archive/{deflate-fallback.js → compression/deflate-fallback.js} +1 -1
- package/dist/esm/modules/archive/{streaming-compress.browser.js → compression/streaming-compress.browser.js} +3 -3
- package/dist/esm/modules/archive/{streaming-compress.js → compression/streaming-compress.js} +2 -2
- package/dist/esm/modules/archive/defaults.js +6 -3
- package/dist/esm/modules/archive/index.base.js +3 -6
- package/dist/esm/modules/archive/index.browser.js +3 -7
- package/dist/esm/modules/archive/index.js +3 -5
- package/dist/esm/modules/archive/internal/byte-queue.js +407 -0
- package/dist/esm/modules/archive/io/archive-sink.js +77 -0
- package/dist/esm/modules/archive/io/archive-source.js +107 -0
- package/dist/esm/modules/archive/unzip/index.js +164 -0
- package/dist/esm/modules/archive/unzip/stream.base.js +1022 -0
- package/dist/esm/modules/archive/{parse.browser.js → unzip/stream.browser.js} +376 -110
- package/dist/{browser/modules/archive/parse.js → esm/modules/archive/unzip/stream.js} +7 -6
- package/dist/{browser/modules/archive → esm/modules/archive/unzip}/zip-parser.js +38 -24
- package/dist/esm/modules/archive/utils/async-queue.js +103 -0
- package/dist/esm/modules/archive/utils/bytes.js +16 -16
- package/dist/esm/modules/archive/utils/compressibility.js +57 -0
- package/dist/esm/modules/archive/utils/parse-buffer.js +21 -23
- package/dist/esm/modules/archive/utils/pattern-scanner.js +27 -0
- package/dist/esm/modules/archive/utils/timestamps.js +62 -1
- package/dist/esm/modules/archive/utils/zip-extra-fields.js +26 -14
- package/dist/esm/modules/archive/zip/index.js +157 -0
- package/dist/esm/modules/archive/{streaming-zip.js → zip/stream.js} +192 -48
- package/dist/esm/modules/archive/zip/zip-bytes.js +239 -0
- package/dist/{browser/modules/archive → esm/modules/archive/zip}/zip-entry-metadata.js +3 -3
- package/dist/esm/modules/archive/zip-spec/zip-records.js +126 -0
- package/dist/esm/modules/excel/form-control.js +54 -16
- package/dist/esm/modules/excel/stream/workbook-reader.browser.js +1 -1
- package/dist/esm/modules/excel/stream/workbook-writer.browser.js +1 -1
- package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +17 -3
- package/dist/esm/modules/excel/xlsx/xlsx.browser.js +3 -6
- package/dist/esm/modules/excel/xlsx/xlsx.js +1 -1
- package/dist/esm/modules/stream/streams.browser.js +850 -697
- package/dist/esm/modules/stream/streams.js +140 -58
- package/dist/iife/THIRD_PARTY_NOTICES.md +81 -0
- package/dist/iife/excelts.iife.js +4777 -2863
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +103 -31
- package/dist/types/index.browser.d.ts +1 -0
- package/dist/{browser/modules/archive → types/modules/archive/compression}/compress.base.d.ts +1 -0
- package/dist/{browser/modules/archive → types/modules/archive/compression}/compress.browser.d.ts +10 -8
- package/dist/types/modules/archive/{streaming-compress.browser.d.ts → compression/streaming-compress.browser.d.ts} +1 -1
- package/dist/types/modules/archive/defaults.d.ts +1 -0
- package/dist/types/modules/archive/index.base.d.ts +4 -4
- package/dist/types/modules/archive/index.browser.d.ts +3 -4
- package/dist/types/modules/archive/index.d.ts +3 -4
- package/dist/types/modules/archive/internal/byte-queue.d.ts +33 -0
- package/dist/types/modules/archive/io/archive-sink.d.ts +9 -0
- package/dist/types/modules/archive/io/archive-source.d.ts +8 -0
- package/dist/types/modules/archive/unzip/index.d.ts +40 -0
- package/dist/types/modules/archive/{parse.base.d.ts → unzip/stream.base.d.ts} +60 -5
- package/dist/types/modules/archive/{parse.browser.d.ts → unzip/stream.browser.d.ts} +2 -2
- package/dist/types/modules/archive/{parse.d.ts → unzip/stream.d.ts} +3 -3
- package/dist/{browser/modules/archive → types/modules/archive/unzip}/zip-parser.d.ts +1 -1
- package/dist/types/modules/archive/utils/async-queue.d.ts +7 -0
- package/dist/types/modules/archive/utils/compressibility.d.ts +10 -0
- package/dist/types/modules/archive/utils/pattern-scanner.d.ts +21 -0
- package/dist/types/modules/archive/utils/zip-extra-fields.d.ts +1 -1
- package/dist/types/modules/archive/zip/index.d.ts +42 -0
- package/dist/types/modules/archive/{streaming-zip.d.ts → zip/stream.d.ts} +29 -6
- package/dist/types/modules/archive/zip/zip-bytes.d.ts +73 -0
- package/dist/types/modules/archive/{zip-entry-metadata.d.ts → zip/zip-entry-metadata.d.ts} +1 -1
- package/dist/types/modules/archive/{zip-records.d.ts → zip-spec/zip-records.d.ts} +20 -0
- package/dist/types/modules/excel/form-control.d.ts +2 -0
- package/dist/types/modules/excel/stream/workbook-writer.browser.d.ts +1 -1
- package/dist/types/modules/stream/streams.browser.d.ts +28 -28
- package/package.json +10 -6
- package/dist/browser/modules/archive/byte-queue.d.ts +0 -18
- package/dist/browser/modules/archive/byte-queue.js +0 -125
- package/dist/browser/modules/archive/parse.base.js +0 -610
- package/dist/browser/modules/archive/utils/zip-extra.d.ts +0 -18
- package/dist/browser/modules/archive/utils/zip-extra.js +0 -68
- package/dist/browser/modules/archive/zip-builder.d.ts +0 -117
- package/dist/browser/modules/archive/zip-builder.js +0 -292
- package/dist/browser/modules/archive/zip-constants.d.ts +0 -18
- package/dist/browser/modules/archive/zip-constants.js +0 -23
- package/dist/browser/modules/archive/zip-records.js +0 -84
- package/dist/cjs/modules/archive/byte-queue.js +0 -129
- package/dist/cjs/modules/archive/parse.base.js +0 -632
- package/dist/cjs/modules/archive/utils/zip-extra.js +0 -74
- package/dist/cjs/modules/archive/zip-builder.js +0 -297
- package/dist/cjs/modules/archive/zip-constants.js +0 -26
- package/dist/cjs/modules/archive/zip-records.js +0 -90
- package/dist/esm/modules/archive/byte-queue.js +0 -125
- package/dist/esm/modules/archive/parse.base.js +0 -610
- package/dist/esm/modules/archive/utils/zip-extra.js +0 -68
- package/dist/esm/modules/archive/zip-builder.js +0 -292
- package/dist/esm/modules/archive/zip-constants.js +0 -23
- package/dist/esm/modules/archive/zip-records.js +0 -84
- package/dist/types/modules/archive/byte-queue.d.ts +0 -18
- package/dist/types/modules/archive/utils/zip-extra.d.ts +0 -18
- package/dist/types/modules/archive/zip-builder.d.ts +0 -117
- package/dist/types/modules/archive/zip-constants.d.ts +0 -18
- /package/dist/browser/modules/archive/{crc32.base.d.ts → compression/crc32.base.d.ts} +0 -0
- /package/dist/browser/modules/archive/{crc32.base.js → compression/crc32.base.js} +0 -0
- /package/dist/browser/modules/archive/{crc32.browser.js → compression/crc32.browser.js} +0 -0
- /package/dist/browser/modules/archive/{deflate-fallback.d.ts → compression/deflate-fallback.d.ts} +0 -0
- /package/dist/browser/modules/archive/{streaming-compress.base.d.ts → compression/streaming-compress.base.d.ts} +0 -0
- /package/dist/browser/modules/archive/{streaming-compress.base.js → compression/streaming-compress.base.js} +0 -0
- /package/dist/browser/modules/archive/{extract.js → unzip/extract.js} +0 -0
- /package/dist/browser/modules/archive/{zip-entry-metadata.d.ts → zip/zip-entry-metadata.d.ts} +0 -0
- /package/dist/browser/modules/archive/{zip-entry-info.d.ts → zip-spec/zip-entry-info.d.ts} +0 -0
- /package/dist/browser/modules/archive/{zip-entry-info.js → zip-spec/zip-entry-info.js} +0 -0
- /package/dist/cjs/modules/archive/{crc32.base.js → compression/crc32.base.js} +0 -0
- /package/dist/cjs/modules/archive/{crc32.browser.js → compression/crc32.browser.js} +0 -0
- /package/dist/cjs/modules/archive/{streaming-compress.base.js → compression/streaming-compress.base.js} +0 -0
- /package/dist/cjs/modules/archive/{extract.js → unzip/extract.js} +0 -0
- /package/dist/cjs/modules/archive/{zip-entry-info.js → zip-spec/zip-entry-info.js} +0 -0
- /package/dist/esm/modules/archive/{crc32.base.js → compression/crc32.base.js} +0 -0
- /package/dist/esm/modules/archive/{crc32.browser.js → compression/crc32.browser.js} +0 -0
- /package/dist/esm/modules/archive/{streaming-compress.base.js → compression/streaming-compress.base.js} +0 -0
- /package/dist/esm/modules/archive/{extract.js → unzip/extract.js} +0 -0
- /package/dist/esm/modules/archive/{zip-entry-info.js → zip-spec/zip-entry-info.js} +0 -0
- /package/dist/{LICENSE → iife/LICENSE} +0 -0
- /package/dist/types/modules/archive/{compress.d.ts → compression/compress.d.ts} +0 -0
- /package/dist/types/modules/archive/{crc32.base.d.ts → compression/crc32.base.d.ts} +0 -0
- /package/dist/types/modules/archive/{crc32.browser.d.ts → compression/crc32.browser.d.ts} +0 -0
- /package/dist/types/modules/archive/{crc32.d.ts → compression/crc32.d.ts} +0 -0
- /package/dist/types/modules/archive/{deflate-fallback.d.ts → compression/deflate-fallback.d.ts} +0 -0
- /package/dist/types/modules/archive/{streaming-compress.base.d.ts → compression/streaming-compress.base.d.ts} +0 -0
- /package/dist/types/modules/archive/{streaming-compress.d.ts → compression/streaming-compress.d.ts} +0 -0
- /package/dist/types/modules/archive/{extract.d.ts → unzip/extract.d.ts} +0 -0
- /package/dist/types/modules/archive/{zip-entry-info.d.ts → zip-spec/zip-entry-info.d.ts} +0 -0
|
@@ -14,6 +14,40 @@ import { EventEmitter } from "./event-emitter.js";
|
|
|
14
14
|
import { PullStream as StandalonePullStream } from "./pull-stream.js";
|
|
15
15
|
import { BufferedStream as StandaloneBufferedStream, StringChunk as StandaloneStringChunk, BufferChunk as StandaloneBufferChunk } from "./buffered-stream.js";
|
|
16
16
|
import { concatUint8Arrays, getTextDecoder, textDecoder } from "./shared.js";
|
|
17
|
+
const removeEmitterListener = (emitter, event, listener) => {
|
|
18
|
+
if (typeof emitter.off === "function") {
|
|
19
|
+
emitter.off(event, listener);
|
|
20
|
+
}
|
|
21
|
+
else if (typeof emitter.removeListener === "function") {
|
|
22
|
+
emitter.removeListener(event, listener);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
const addEmitterListener = (emitter, event, listener, options) => {
|
|
26
|
+
if (options?.once && typeof emitter.once === "function") {
|
|
27
|
+
emitter.once(event, listener);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
emitter.on(event, listener);
|
|
31
|
+
}
|
|
32
|
+
return () => removeEmitterListener(emitter, event, listener);
|
|
33
|
+
};
|
|
34
|
+
const createListenerRegistry = () => {
|
|
35
|
+
const listeners = [];
|
|
36
|
+
return {
|
|
37
|
+
add: (emitter, event, listener) => {
|
|
38
|
+
listeners.push(addEmitterListener(emitter, event, listener));
|
|
39
|
+
},
|
|
40
|
+
once: (emitter, event, listener) => {
|
|
41
|
+
listeners.push(addEmitterListener(emitter, event, listener, { once: true }));
|
|
42
|
+
},
|
|
43
|
+
cleanup: () => {
|
|
44
|
+
for (let i = listeners.length - 1; i >= 0; i--) {
|
|
45
|
+
listeners[i]();
|
|
46
|
+
}
|
|
47
|
+
listeners.length = 0;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
};
|
|
17
51
|
// =============================================================================
|
|
18
52
|
// Readable Stream Wrapper
|
|
19
53
|
// =============================================================================
|
|
@@ -30,6 +64,7 @@ export class Readable extends EventEmitter {
|
|
|
30
64
|
this._bufferSize = 0;
|
|
31
65
|
this._reading = false;
|
|
32
66
|
this._ended = false;
|
|
67
|
+
this._endEmitted = false;
|
|
33
68
|
this._destroyed = false;
|
|
34
69
|
this._errored = null;
|
|
35
70
|
this._closed = false;
|
|
@@ -86,20 +121,7 @@ export class Readable extends EventEmitter {
|
|
|
86
121
|
*/
|
|
87
122
|
static from(iterable, options) {
|
|
88
123
|
const readable = new Readable({ ...options, objectMode: options?.objectMode ?? true });
|
|
89
|
-
(
|
|
90
|
-
try {
|
|
91
|
-
for await (const chunk of iterable) {
|
|
92
|
-
if (!readable.push(chunk)) {
|
|
93
|
-
// Backpressure
|
|
94
|
-
await new Promise(resolve => setTimeout(resolve, 0));
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
readable.push(null);
|
|
98
|
-
}
|
|
99
|
-
catch (err) {
|
|
100
|
-
readable.destroy(err);
|
|
101
|
-
}
|
|
102
|
-
})();
|
|
124
|
+
pumpAsyncIterableToReadable(readable, toAsyncIterable(iterable));
|
|
103
125
|
return readable;
|
|
104
126
|
}
|
|
105
127
|
/**
|
|
@@ -144,16 +166,30 @@ export class Readable extends EventEmitter {
|
|
|
144
166
|
// Controller may already be closed
|
|
145
167
|
}
|
|
146
168
|
}
|
|
147
|
-
|
|
169
|
+
// Emit 'end' only after buffered data is fully drained.
|
|
170
|
+
// This avoids premature 'end' when producers push null while paused.
|
|
171
|
+
if (this._bufferedLength() === 0) {
|
|
172
|
+
this._emitEndOnce();
|
|
173
|
+
}
|
|
148
174
|
// Note: Don't call destroy() here, let the stream be consumed naturally
|
|
149
175
|
// The reader will return done:true when it finishes reading
|
|
150
176
|
return false;
|
|
151
177
|
}
|
|
178
|
+
// Keep the internal Web ReadableStream in sync for controllable streams.
|
|
179
|
+
// For external Web streams (_webStreamMode=true), push() is not the data source.
|
|
180
|
+
if (controller && !this._webStreamMode) {
|
|
181
|
+
try {
|
|
182
|
+
controller.enqueue(chunk);
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
// Controller may be closed/errored; Node-side buffering/events still work.
|
|
186
|
+
}
|
|
187
|
+
}
|
|
152
188
|
if (this._flowing) {
|
|
153
189
|
// In flowing mode, emit data directly without buffering or enqueueing
|
|
154
190
|
// const chunkStr = chunk instanceof Uint8Array ? new TextDecoder().decode(chunk.slice(0, 50)) : String(chunk).slice(0, 50);
|
|
155
191
|
// console.log(`[Readable#${this._id}.push FLOWING] emit data size:${(chunk as any).length || (chunk as any).byteLength} start:"${chunkStr}"`);
|
|
156
|
-
this.emit("data", chunk);
|
|
192
|
+
this.emit("data", this._applyEncoding(chunk));
|
|
157
193
|
// Check if stream was paused during emit (backpressure from consumer)
|
|
158
194
|
if (!this._flowing) {
|
|
159
195
|
return false;
|
|
@@ -178,10 +214,8 @@ export class Readable extends EventEmitter {
|
|
|
178
214
|
if (!this.objectMode) {
|
|
179
215
|
this._bufferSize += this._getChunkSize(chunk);
|
|
180
216
|
}
|
|
181
|
-
// NOTE:
|
|
182
|
-
//
|
|
183
|
-
// Web Stream is only used for async iteration when not in push mode.
|
|
184
|
-
// Enqueueing here would cause data duplication when _startReading is also running.
|
|
217
|
+
// NOTE: We still buffer for Node-style read()/data semantics.
|
|
218
|
+
// The internal Web ReadableStream is also fed via controller.enqueue() above.
|
|
185
219
|
// Emit readable event when buffer goes from empty to having data
|
|
186
220
|
if (wasEmpty) {
|
|
187
221
|
queueMicrotask(() => this.emit("readable"));
|
|
@@ -195,6 +229,13 @@ export class Readable extends EventEmitter {
|
|
|
195
229
|
return this._bufferSize < this.readableHighWaterMark;
|
|
196
230
|
}
|
|
197
231
|
}
|
|
232
|
+
_emitEndOnce() {
|
|
233
|
+
if (this._endEmitted) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
this._endEmitted = true;
|
|
237
|
+
this.emit("end");
|
|
238
|
+
}
|
|
198
239
|
/**
|
|
199
240
|
* Put a chunk back at the front of the buffer
|
|
200
241
|
* Note: unshift is allowed even after end, as it's used to put back already read data
|
|
@@ -219,14 +260,22 @@ export class Readable extends EventEmitter {
|
|
|
219
260
|
if (!this.objectMode) {
|
|
220
261
|
this._bufferSize -= this._getChunkSize(chunk);
|
|
221
262
|
}
|
|
222
|
-
|
|
263
|
+
const decoded = this._applyEncoding(chunk);
|
|
264
|
+
if (this._ended && this._bufferedLength() === 0) {
|
|
265
|
+
queueMicrotask(() => this._emitEndOnce());
|
|
266
|
+
}
|
|
267
|
+
return decoded;
|
|
223
268
|
}
|
|
224
269
|
// For binary mode, handle size
|
|
225
270
|
const chunk = this._bufferShift();
|
|
226
271
|
if (!this.objectMode) {
|
|
227
272
|
this._bufferSize -= this._getChunkSize(chunk);
|
|
228
273
|
}
|
|
229
|
-
|
|
274
|
+
const decoded = this._applyEncoding(chunk);
|
|
275
|
+
if (this._ended && this._bufferedLength() === 0) {
|
|
276
|
+
queueMicrotask(() => this._emitEndOnce());
|
|
277
|
+
}
|
|
278
|
+
return decoded;
|
|
230
279
|
}
|
|
231
280
|
return null;
|
|
232
281
|
}
|
|
@@ -332,11 +381,11 @@ export class Readable extends EventEmitter {
|
|
|
332
381
|
if (!this.objectMode) {
|
|
333
382
|
this._bufferSize -= this._getChunkSize(chunk);
|
|
334
383
|
}
|
|
335
|
-
this.emit("data", chunk);
|
|
384
|
+
this.emit("data", this._applyEncoding(chunk));
|
|
336
385
|
}
|
|
337
386
|
// If already ended, emit end event
|
|
338
387
|
if (this._ended && this._bufferedLength() === 0) {
|
|
339
|
-
this.
|
|
388
|
+
this._emitEndOnce();
|
|
340
389
|
}
|
|
341
390
|
else if (this._read) {
|
|
342
391
|
// Call user-provided read function asynchronously
|
|
@@ -384,58 +433,72 @@ export class Readable extends EventEmitter {
|
|
|
384
433
|
// causing `instanceof Transform/Writable/Duplex` to fail even when the object
|
|
385
434
|
// is a valid destination.
|
|
386
435
|
const dest = destination;
|
|
387
|
-
//
|
|
388
|
-
//
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
const
|
|
392
|
-
const
|
|
393
|
-
const
|
|
394
|
-
const
|
|
395
|
-
|
|
436
|
+
// For event handling (drain, once, off), we need the object that emits events.
|
|
437
|
+
// For write/end, we must call the destination's own write()/end() methods,
|
|
438
|
+
// NOT the internal _writable, because Transform.write() has important logic
|
|
439
|
+
// (like auto-consume) that _writable.write() bypasses.
|
|
440
|
+
const eventTarget = dest;
|
|
441
|
+
const hasWrite = typeof dest?.write === "function";
|
|
442
|
+
const hasEnd = typeof dest?.end === "function";
|
|
443
|
+
const hasOn = typeof eventTarget?.on === "function";
|
|
444
|
+
const hasOnce = typeof eventTarget?.once === "function";
|
|
445
|
+
const hasOff = typeof eventTarget?.off === "function";
|
|
446
|
+
if (!hasWrite || !hasEnd || (!hasOnce && !hasOn) || (!hasOff && !eventTarget?.removeListener)) {
|
|
396
447
|
throw new Error("Readable.pipe: invalid destination");
|
|
397
448
|
}
|
|
398
|
-
|
|
399
|
-
this._pipeTo.push(target);
|
|
449
|
+
this._pipeTo.push(dest);
|
|
400
450
|
// Create listeners that we can later remove
|
|
451
|
+
let drainListener;
|
|
452
|
+
const removeDrainListener = () => {
|
|
453
|
+
if (!drainListener) {
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
if (typeof eventTarget.off === "function") {
|
|
457
|
+
eventTarget.off("drain", drainListener);
|
|
458
|
+
}
|
|
459
|
+
else if (typeof eventTarget.removeListener === "function") {
|
|
460
|
+
eventTarget.removeListener("drain", drainListener);
|
|
461
|
+
}
|
|
462
|
+
drainListener = undefined;
|
|
463
|
+
};
|
|
401
464
|
const dataListener = (chunk) => {
|
|
402
|
-
|
|
465
|
+
// Call destination's write() method (not internal _writable.write())
|
|
466
|
+
// This ensures Transform.write() logic runs properly
|
|
467
|
+
const canWrite = dest.write(chunk);
|
|
403
468
|
if (!canWrite) {
|
|
404
469
|
this.pause();
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
const resumeOnce = () => {
|
|
410
|
-
if (typeof target.off === "function") {
|
|
411
|
-
target.off("drain", resumeOnce);
|
|
412
|
-
}
|
|
413
|
-
else if (typeof target.removeListener === "function") {
|
|
414
|
-
target.removeListener("drain", resumeOnce);
|
|
415
|
-
}
|
|
470
|
+
// Install a removable, once-style drain listener.
|
|
471
|
+
if (!drainListener) {
|
|
472
|
+
drainListener = () => {
|
|
473
|
+
removeDrainListener();
|
|
416
474
|
this.resume();
|
|
417
475
|
};
|
|
418
|
-
|
|
476
|
+
eventTarget.on("drain", drainListener);
|
|
477
|
+
const entry = this._pipeListeners.get(dest);
|
|
478
|
+
if (entry) {
|
|
479
|
+
entry.drain = drainListener;
|
|
480
|
+
}
|
|
419
481
|
}
|
|
420
482
|
}
|
|
421
483
|
};
|
|
422
484
|
const endListener = () => {
|
|
423
|
-
|
|
485
|
+
dest.end();
|
|
424
486
|
};
|
|
425
487
|
const errorListener = (err) => {
|
|
426
|
-
if (typeof
|
|
427
|
-
|
|
488
|
+
if (typeof dest.destroy === "function") {
|
|
489
|
+
dest.destroy(err);
|
|
428
490
|
}
|
|
429
491
|
else {
|
|
430
492
|
// Best-effort: forward error to the destination if it supports events.
|
|
431
|
-
|
|
493
|
+
eventTarget.emit?.("error", err);
|
|
432
494
|
}
|
|
433
495
|
};
|
|
434
496
|
// Store listeners for later removal in unpipe
|
|
435
|
-
this._pipeListeners.set(
|
|
497
|
+
this._pipeListeners.set(dest, {
|
|
436
498
|
data: dataListener,
|
|
437
499
|
end: endListener,
|
|
438
|
-
error: errorListener
|
|
500
|
+
error: errorListener,
|
|
501
|
+
eventTarget
|
|
439
502
|
});
|
|
440
503
|
this.on("data", dataListener);
|
|
441
504
|
this.once("end", endListener);
|
|
@@ -458,6 +521,14 @@ export class Readable extends EventEmitter {
|
|
|
458
521
|
this.off("data", listeners.data);
|
|
459
522
|
this.off("end", listeners.end);
|
|
460
523
|
this.off("error", listeners.error);
|
|
524
|
+
if (listeners.drain) {
|
|
525
|
+
if (typeof listeners.eventTarget?.off === "function") {
|
|
526
|
+
listeners.eventTarget.off("drain", listeners.drain);
|
|
527
|
+
}
|
|
528
|
+
else if (typeof listeners.eventTarget?.removeListener === "function") {
|
|
529
|
+
listeners.eventTarget.removeListener("drain", listeners.drain);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
461
532
|
this._pipeListeners.delete(destination);
|
|
462
533
|
}
|
|
463
534
|
}
|
|
@@ -469,6 +540,14 @@ export class Readable extends EventEmitter {
|
|
|
469
540
|
this.off("data", listeners.data);
|
|
470
541
|
this.off("end", listeners.end);
|
|
471
542
|
this.off("error", listeners.error);
|
|
543
|
+
if (listeners.drain) {
|
|
544
|
+
if (typeof listeners.eventTarget?.off === "function") {
|
|
545
|
+
listeners.eventTarget.off("drain", listeners.drain);
|
|
546
|
+
}
|
|
547
|
+
else if (typeof listeners.eventTarget?.removeListener === "function") {
|
|
548
|
+
listeners.eventTarget.removeListener("drain", listeners.drain);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
472
551
|
this._pipeListeners.delete(target);
|
|
473
552
|
}
|
|
474
553
|
}
|
|
@@ -487,12 +566,26 @@ export class Readable extends EventEmitter {
|
|
|
487
566
|
}
|
|
488
567
|
this._destroyed = true;
|
|
489
568
|
this._ended = true;
|
|
569
|
+
// Ensure we detach from destinations to avoid leaking listeners.
|
|
570
|
+
this.unpipe();
|
|
490
571
|
if (error) {
|
|
491
572
|
this._errored = error;
|
|
492
573
|
this.emit("error", error);
|
|
493
574
|
}
|
|
494
575
|
if (this._reader) {
|
|
495
|
-
this._reader
|
|
576
|
+
const reader = this._reader;
|
|
577
|
+
this._reader = null;
|
|
578
|
+
reader
|
|
579
|
+
.cancel()
|
|
580
|
+
.catch(() => { })
|
|
581
|
+
.finally(() => {
|
|
582
|
+
try {
|
|
583
|
+
reader.releaseLock();
|
|
584
|
+
}
|
|
585
|
+
catch {
|
|
586
|
+
// Ignore if a read is still pending
|
|
587
|
+
}
|
|
588
|
+
});
|
|
496
589
|
}
|
|
497
590
|
this._closed = true;
|
|
498
591
|
this.emit("close");
|
|
@@ -571,18 +664,38 @@ export class Readable extends EventEmitter {
|
|
|
571
664
|
const { done, value } = await this._reader.read();
|
|
572
665
|
// Check _pushMode again after async read - if push() was called, stop reading
|
|
573
666
|
if (this._pushMode) {
|
|
667
|
+
if (this._reader) {
|
|
668
|
+
const reader = this._reader;
|
|
669
|
+
this._reader = null;
|
|
670
|
+
try {
|
|
671
|
+
reader.releaseLock();
|
|
672
|
+
}
|
|
673
|
+
catch {
|
|
674
|
+
// Ignore if a read is still pending
|
|
675
|
+
}
|
|
676
|
+
}
|
|
574
677
|
break;
|
|
575
678
|
}
|
|
576
679
|
if (done) {
|
|
577
680
|
this._ended = true;
|
|
578
|
-
this.
|
|
681
|
+
this._emitEndOnce();
|
|
682
|
+
if (this._reader) {
|
|
683
|
+
const reader = this._reader;
|
|
684
|
+
this._reader = null;
|
|
685
|
+
try {
|
|
686
|
+
reader.releaseLock();
|
|
687
|
+
}
|
|
688
|
+
catch {
|
|
689
|
+
// Ignore if a read is still pending
|
|
690
|
+
}
|
|
691
|
+
}
|
|
579
692
|
break;
|
|
580
693
|
}
|
|
581
694
|
if (value !== undefined) {
|
|
582
695
|
// In flowing mode, emit data directly without buffering
|
|
583
696
|
// Only buffer if not flowing (paused mode)
|
|
584
697
|
if (this._flowing) {
|
|
585
|
-
this.emit("data", value);
|
|
698
|
+
this.emit("data", this._applyEncoding(value));
|
|
586
699
|
}
|
|
587
700
|
else {
|
|
588
701
|
this._buffer.push(value);
|
|
@@ -595,6 +708,16 @@ export class Readable extends EventEmitter {
|
|
|
595
708
|
}
|
|
596
709
|
catch (err) {
|
|
597
710
|
this.emit("error", err);
|
|
711
|
+
if (this._reader) {
|
|
712
|
+
const reader = this._reader;
|
|
713
|
+
this._reader = null;
|
|
714
|
+
try {
|
|
715
|
+
reader.releaseLock();
|
|
716
|
+
}
|
|
717
|
+
catch {
|
|
718
|
+
// Ignore if a read is still pending
|
|
719
|
+
}
|
|
720
|
+
}
|
|
598
721
|
}
|
|
599
722
|
finally {
|
|
600
723
|
this._reading = false;
|
|
@@ -602,7 +725,8 @@ export class Readable extends EventEmitter {
|
|
|
602
725
|
}
|
|
603
726
|
/**
|
|
604
727
|
* Async iterator support
|
|
605
|
-
* Uses
|
|
728
|
+
* Uses a unified event-queue iterator with simple backpressure.
|
|
729
|
+
* This matches Node's behavior more closely (iterator drives flowing mode).
|
|
606
730
|
*/
|
|
607
731
|
async *[Symbol.asyncIterator]() {
|
|
608
732
|
// First yield any buffered data
|
|
@@ -611,117 +735,124 @@ export class Readable extends EventEmitter {
|
|
|
611
735
|
if (!this.objectMode) {
|
|
612
736
|
this._bufferSize -= this._getChunkSize(chunk);
|
|
613
737
|
}
|
|
614
|
-
yield chunk;
|
|
738
|
+
yield this._applyEncoding(chunk);
|
|
615
739
|
}
|
|
616
|
-
// If already ended, we're done
|
|
617
740
|
if (this._ended) {
|
|
618
741
|
return;
|
|
619
742
|
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
let resolveNext = null;
|
|
626
|
-
let rejectNext = null;
|
|
627
|
-
let done = false;
|
|
628
|
-
let streamError = null;
|
|
629
|
-
let dataQueueIndex = 0;
|
|
630
|
-
const dataHandler = (chunk) => {
|
|
631
|
-
if (resolveNext) {
|
|
632
|
-
resolveNext(chunk);
|
|
633
|
-
resolveNext = null;
|
|
634
|
-
rejectNext = null;
|
|
635
|
-
}
|
|
636
|
-
else {
|
|
637
|
-
dataQueue.push(chunk);
|
|
638
|
-
}
|
|
639
|
-
};
|
|
640
|
-
const endHandler = () => {
|
|
641
|
-
done = true;
|
|
642
|
-
if (resolveNext) {
|
|
643
|
-
resolveNext(null);
|
|
644
|
-
resolveNext = null;
|
|
645
|
-
rejectNext = null;
|
|
646
|
-
}
|
|
647
|
-
};
|
|
648
|
-
const errorHandler = (err) => {
|
|
649
|
-
done = true;
|
|
650
|
-
streamError = err;
|
|
651
|
-
if (rejectNext) {
|
|
652
|
-
rejectNext(err);
|
|
653
|
-
resolveNext = null;
|
|
654
|
-
rejectNext = null;
|
|
655
|
-
}
|
|
656
|
-
};
|
|
657
|
-
const closeHandler = () => {
|
|
658
|
-
// If stream closed without end event (e.g., after destroy()),
|
|
659
|
-
// treat it as done
|
|
660
|
-
done = true;
|
|
661
|
-
if (resolveNext) {
|
|
662
|
-
resolveNext(null);
|
|
663
|
-
resolveNext = null;
|
|
664
|
-
rejectNext = null;
|
|
665
|
-
}
|
|
666
|
-
};
|
|
667
|
-
this.on("data", dataHandler);
|
|
668
|
-
this.on("end", endHandler);
|
|
669
|
-
this.on("error", errorHandler);
|
|
670
|
-
this.on("close", closeHandler);
|
|
671
|
-
try {
|
|
672
|
-
// Enter flowing mode
|
|
673
|
-
this.resume();
|
|
674
|
-
while (!done || dataQueueIndex < dataQueue.length) {
|
|
675
|
-
// Check for error before processing
|
|
676
|
-
if (streamError) {
|
|
677
|
-
throw streamError;
|
|
678
|
-
}
|
|
679
|
-
if (dataQueueIndex < dataQueue.length) {
|
|
680
|
-
const chunk = dataQueue[dataQueueIndex++];
|
|
681
|
-
if (dataQueueIndex >= 1024 && dataQueueIndex * 2 >= dataQueue.length) {
|
|
682
|
-
dataQueue.splice(0, dataQueueIndex);
|
|
683
|
-
dataQueueIndex = 0;
|
|
684
|
-
}
|
|
685
|
-
yield chunk;
|
|
686
|
-
}
|
|
687
|
-
else if (!done) {
|
|
688
|
-
const chunk = await new Promise((resolve, reject) => {
|
|
689
|
-
resolveNext = resolve;
|
|
690
|
-
rejectNext = reject;
|
|
691
|
-
});
|
|
692
|
-
if (chunk !== null) {
|
|
693
|
-
yield chunk;
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
// Check for error after loop
|
|
698
|
-
if (streamError) {
|
|
699
|
-
throw streamError;
|
|
700
|
-
}
|
|
743
|
+
const highWaterMark = this.readableHighWaterMark;
|
|
744
|
+
const lowWaterMark = Math.max(0, Math.floor(highWaterMark / 2));
|
|
745
|
+
const chunkSizeForBackpressure = (chunk) => {
|
|
746
|
+
if (this.objectMode) {
|
|
747
|
+
return 1;
|
|
701
748
|
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
this.off("end", endHandler);
|
|
705
|
-
this.off("error", errorHandler);
|
|
706
|
-
this.off("close", closeHandler);
|
|
749
|
+
if (chunk instanceof Uint8Array) {
|
|
750
|
+
return chunk.byteLength;
|
|
707
751
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
752
|
+
if (typeof chunk === "string") {
|
|
753
|
+
return chunk.length;
|
|
754
|
+
}
|
|
755
|
+
return 1;
|
|
756
|
+
};
|
|
757
|
+
const dataQueue = [];
|
|
758
|
+
let dataQueueIndex = 0;
|
|
759
|
+
let queuedSize = 0;
|
|
760
|
+
let resolveNext = null;
|
|
761
|
+
let rejectNext = null;
|
|
762
|
+
let done = false;
|
|
763
|
+
let pausedByIterator = false;
|
|
764
|
+
let streamError = null;
|
|
765
|
+
const dataHandler = (chunk) => {
|
|
766
|
+
// data events are already encoding-aware; do not decode again here.
|
|
767
|
+
if (resolveNext) {
|
|
768
|
+
resolveNext(chunk);
|
|
769
|
+
resolveNext = null;
|
|
770
|
+
rejectNext = null;
|
|
771
|
+
}
|
|
772
|
+
else {
|
|
773
|
+
dataQueue.push(chunk);
|
|
774
|
+
}
|
|
775
|
+
queuedSize += chunkSizeForBackpressure(chunk);
|
|
776
|
+
if (!pausedByIterator && queuedSize >= highWaterMark) {
|
|
777
|
+
pausedByIterator = true;
|
|
778
|
+
this.pause();
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
const endHandler = () => {
|
|
782
|
+
done = true;
|
|
783
|
+
if (resolveNext) {
|
|
784
|
+
resolveNext(null);
|
|
785
|
+
resolveNext = null;
|
|
786
|
+
rejectNext = null;
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
const closeHandler = () => {
|
|
790
|
+
done = true;
|
|
791
|
+
if (resolveNext) {
|
|
792
|
+
resolveNext(null);
|
|
793
|
+
resolveNext = null;
|
|
794
|
+
rejectNext = null;
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
const errorHandler = (err) => {
|
|
798
|
+
done = true;
|
|
799
|
+
streamError = err;
|
|
800
|
+
if (rejectNext) {
|
|
801
|
+
rejectNext(err);
|
|
802
|
+
resolveNext = null;
|
|
803
|
+
rejectNext = null;
|
|
804
|
+
}
|
|
805
|
+
};
|
|
806
|
+
this.on("data", dataHandler);
|
|
807
|
+
this.on("end", endHandler);
|
|
808
|
+
this.on("error", errorHandler);
|
|
809
|
+
this.on("close", closeHandler);
|
|
714
810
|
try {
|
|
811
|
+
// Iterator consumption should drive the stream.
|
|
812
|
+
this.resume();
|
|
715
813
|
while (true) {
|
|
716
|
-
|
|
814
|
+
if (streamError) {
|
|
815
|
+
throw streamError;
|
|
816
|
+
}
|
|
817
|
+
if (dataQueueIndex < dataQueue.length) {
|
|
818
|
+
const chunk = dataQueue[dataQueueIndex++];
|
|
819
|
+
queuedSize -= chunkSizeForBackpressure(chunk);
|
|
820
|
+
if (dataQueueIndex >= 1024 && dataQueueIndex * 2 >= dataQueue.length) {
|
|
821
|
+
dataQueue.splice(0, dataQueueIndex);
|
|
822
|
+
dataQueueIndex = 0;
|
|
823
|
+
}
|
|
824
|
+
if (pausedByIterator && queuedSize <= lowWaterMark && !done && !this._destroyed) {
|
|
825
|
+
pausedByIterator = false;
|
|
826
|
+
this.resume();
|
|
827
|
+
}
|
|
828
|
+
yield chunk;
|
|
829
|
+
continue;
|
|
830
|
+
}
|
|
717
831
|
if (done) {
|
|
718
832
|
break;
|
|
719
833
|
}
|
|
720
|
-
|
|
834
|
+
const chunk = await new Promise((resolve, reject) => {
|
|
835
|
+
resolveNext = resolve;
|
|
836
|
+
rejectNext = reject;
|
|
837
|
+
});
|
|
838
|
+
if (chunk !== null) {
|
|
839
|
+
queuedSize -= chunkSizeForBackpressure(chunk);
|
|
840
|
+
if (pausedByIterator && queuedSize <= lowWaterMark && !done && !this._destroyed) {
|
|
841
|
+
pausedByIterator = false;
|
|
842
|
+
this.resume();
|
|
843
|
+
}
|
|
844
|
+
yield chunk;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
if (streamError) {
|
|
848
|
+
throw streamError;
|
|
721
849
|
}
|
|
722
850
|
}
|
|
723
851
|
finally {
|
|
724
|
-
this.
|
|
852
|
+
this.off("data", dataHandler);
|
|
853
|
+
this.off("end", endHandler);
|
|
854
|
+
this.off("error", errorHandler);
|
|
855
|
+
this.off("close", closeHandler);
|
|
725
856
|
}
|
|
726
857
|
}
|
|
727
858
|
/**
|
|
@@ -940,6 +1071,32 @@ export class Readable extends EventEmitter {
|
|
|
940
1071
|
return stream;
|
|
941
1072
|
}
|
|
942
1073
|
}
|
|
1074
|
+
function toAsyncIterable(iterable) {
|
|
1075
|
+
if (iterable && typeof iterable[Symbol.asyncIterator] === "function") {
|
|
1076
|
+
return iterable;
|
|
1077
|
+
}
|
|
1078
|
+
return (async function* () {
|
|
1079
|
+
for (const item of iterable) {
|
|
1080
|
+
yield item;
|
|
1081
|
+
}
|
|
1082
|
+
})();
|
|
1083
|
+
}
|
|
1084
|
+
function pumpAsyncIterableToReadable(readable, iterable) {
|
|
1085
|
+
(async () => {
|
|
1086
|
+
try {
|
|
1087
|
+
for await (const chunk of iterable) {
|
|
1088
|
+
if (!readable.push(chunk)) {
|
|
1089
|
+
// Simple backpressure: yield to consumer.
|
|
1090
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
readable.push(null);
|
|
1094
|
+
}
|
|
1095
|
+
catch (err) {
|
|
1096
|
+
readable.destroy(err);
|
|
1097
|
+
}
|
|
1098
|
+
})();
|
|
1099
|
+
}
|
|
943
1100
|
// =============================================================================
|
|
944
1101
|
// Writable Stream Wrapper
|
|
945
1102
|
// =============================================================================
|
|
@@ -957,10 +1114,12 @@ export class Writable extends EventEmitter {
|
|
|
957
1114
|
this._closed = false;
|
|
958
1115
|
this._pendingWrites = 0;
|
|
959
1116
|
this._writableLength = 0;
|
|
1117
|
+
this._needDrain = false;
|
|
960
1118
|
this._corked = 0;
|
|
961
1119
|
this._corkedChunks = [];
|
|
962
1120
|
this._defaultEncoding = "utf8";
|
|
963
1121
|
this._aborted = false;
|
|
1122
|
+
this._ownsStream = false;
|
|
964
1123
|
this.objectMode = options?.objectMode ?? false;
|
|
965
1124
|
this.writableHighWaterMark = options?.highWaterMark ?? 16384;
|
|
966
1125
|
this.autoDestroy = options?.autoDestroy ?? true;
|
|
@@ -976,8 +1135,10 @@ export class Writable extends EventEmitter {
|
|
|
976
1135
|
}
|
|
977
1136
|
if (options?.stream) {
|
|
978
1137
|
this._stream = options.stream;
|
|
1138
|
+
this._ownsStream = false;
|
|
979
1139
|
}
|
|
980
1140
|
else {
|
|
1141
|
+
this._ownsStream = true;
|
|
981
1142
|
// Create bound references to instance properties/methods for use in WritableStream callbacks
|
|
982
1143
|
const getWriteFunc = () => this._writeFunc;
|
|
983
1144
|
const getFinalFunc = () => this._finalFunc;
|
|
@@ -1084,25 +1245,37 @@ export class Writable extends EventEmitter {
|
|
|
1084
1245
|
this._writableLength += chunkSize;
|
|
1085
1246
|
return this._writableLength < this.writableHighWaterMark;
|
|
1086
1247
|
}
|
|
1087
|
-
|
|
1248
|
+
const ok = this._doWrite(chunk, cb);
|
|
1249
|
+
if (!ok) {
|
|
1250
|
+
this._needDrain = true;
|
|
1251
|
+
}
|
|
1252
|
+
return ok;
|
|
1088
1253
|
}
|
|
1089
1254
|
_doWrite(chunk, callback) {
|
|
1090
1255
|
// Track pending writes for writableLength
|
|
1091
1256
|
const chunkSize = this._getChunkSize(chunk);
|
|
1092
1257
|
this._pendingWrites++;
|
|
1093
1258
|
this._writableLength += chunkSize;
|
|
1094
|
-
this._getWriter()
|
|
1259
|
+
const writer = this._getWriter();
|
|
1260
|
+
writer
|
|
1095
1261
|
.write(chunk)
|
|
1096
1262
|
.then(() => {
|
|
1097
1263
|
this._pendingWrites--;
|
|
1098
1264
|
this._writableLength -= chunkSize;
|
|
1099
|
-
this.
|
|
1265
|
+
if (this._needDrain && this._writableLength < this.writableHighWaterMark) {
|
|
1266
|
+
this._needDrain = false;
|
|
1267
|
+
this.emit("drain");
|
|
1268
|
+
}
|
|
1100
1269
|
callback?.(null);
|
|
1101
1270
|
})
|
|
1102
1271
|
.catch(err => {
|
|
1103
1272
|
this._pendingWrites--;
|
|
1104
1273
|
this._writableLength -= chunkSize;
|
|
1105
|
-
|
|
1274
|
+
// Avoid double-emitting if we're already in an errored/destroyed state.
|
|
1275
|
+
if (!this._destroyed) {
|
|
1276
|
+
this._errored = err;
|
|
1277
|
+
this.emit("error", err);
|
|
1278
|
+
}
|
|
1106
1279
|
callback?.(err);
|
|
1107
1280
|
});
|
|
1108
1281
|
// Return false if we've exceeded high water mark (for backpressure)
|
|
@@ -1133,12 +1306,29 @@ export class Writable extends EventEmitter {
|
|
|
1133
1306
|
: callback;
|
|
1134
1307
|
const finish = async () => {
|
|
1135
1308
|
try {
|
|
1309
|
+
const writer = this._getWriter();
|
|
1136
1310
|
if (chunk !== undefined) {
|
|
1137
|
-
await
|
|
1311
|
+
await writer.write(chunk);
|
|
1312
|
+
}
|
|
1313
|
+
await writer.close();
|
|
1314
|
+
if (this._writer === writer) {
|
|
1315
|
+
this._writer = null;
|
|
1316
|
+
try {
|
|
1317
|
+
writer.releaseLock();
|
|
1318
|
+
}
|
|
1319
|
+
catch {
|
|
1320
|
+
// Ignore
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
// If we own the underlying Web WritableStream, its `close()` handler already
|
|
1324
|
+
// emits finish/close. For external streams, we must emit finish ourselves.
|
|
1325
|
+
if (!this._ownsStream) {
|
|
1326
|
+
this._finished = true;
|
|
1327
|
+
this.emit("finish");
|
|
1328
|
+
if (this.emitClose) {
|
|
1329
|
+
this.emit("close");
|
|
1330
|
+
}
|
|
1138
1331
|
}
|
|
1139
|
-
await this._getWriter().close();
|
|
1140
|
-
this._finished = true;
|
|
1141
|
-
this.emit("finish");
|
|
1142
1332
|
if (cb) {
|
|
1143
1333
|
cb();
|
|
1144
1334
|
}
|
|
@@ -1159,12 +1349,24 @@ export class Writable extends EventEmitter {
|
|
|
1159
1349
|
}
|
|
1160
1350
|
this._destroyed = true;
|
|
1161
1351
|
this._ended = true;
|
|
1162
|
-
if (error) {
|
|
1352
|
+
if (error && !this._errored) {
|
|
1163
1353
|
this._errored = error;
|
|
1164
1354
|
this.emit("error", error);
|
|
1165
1355
|
}
|
|
1166
1356
|
if (this._writer) {
|
|
1167
|
-
this._writer
|
|
1357
|
+
const writer = this._writer;
|
|
1358
|
+
this._writer = null;
|
|
1359
|
+
writer
|
|
1360
|
+
.abort(error)
|
|
1361
|
+
.catch(() => { })
|
|
1362
|
+
.finally(() => {
|
|
1363
|
+
try {
|
|
1364
|
+
writer.releaseLock();
|
|
1365
|
+
}
|
|
1366
|
+
catch {
|
|
1367
|
+
// Ignore
|
|
1368
|
+
}
|
|
1369
|
+
});
|
|
1168
1370
|
}
|
|
1169
1371
|
this._closed = true;
|
|
1170
1372
|
this.emit("close");
|
|
@@ -1315,332 +1517,259 @@ export function normalizeWritable(stream) {
|
|
|
1315
1517
|
*/
|
|
1316
1518
|
export class Transform extends EventEmitter {
|
|
1317
1519
|
/**
|
|
1318
|
-
* Push data to the readable side (Node.js compatibility)
|
|
1319
|
-
*
|
|
1520
|
+
* Push data to the readable side (Node.js compatibility).
|
|
1521
|
+
* Intended to be called from within transform/flush.
|
|
1320
1522
|
*/
|
|
1321
1523
|
push(chunk) {
|
|
1322
|
-
|
|
1323
|
-
return false;
|
|
1324
|
-
}
|
|
1325
|
-
if (this._transformController) {
|
|
1326
|
-
// If we're in a transform callback, enqueue directly
|
|
1327
|
-
this._transformController.enqueue(chunk);
|
|
1328
|
-
}
|
|
1329
|
-
else {
|
|
1330
|
-
// Otherwise buffer for later
|
|
1331
|
-
this._pushBuffer.push(chunk);
|
|
1332
|
-
}
|
|
1333
|
-
return true;
|
|
1524
|
+
return this._readable.push(chunk);
|
|
1334
1525
|
}
|
|
1335
1526
|
constructor(options) {
|
|
1336
1527
|
super();
|
|
1337
|
-
this._ended = false;
|
|
1338
1528
|
this._destroyed = false;
|
|
1529
|
+
this._ended = false;
|
|
1339
1530
|
this._errored = false;
|
|
1340
|
-
// Buffer for Node.js style push() calls during transform
|
|
1341
|
-
this._pushBuffer = [];
|
|
1342
|
-
// Controller for enqueueing pushed data (set during transform execution)
|
|
1343
|
-
this._transformController = null;
|
|
1344
|
-
// Buffer for writes that occur after end() but before writable is closed
|
|
1345
|
-
this._pendingEndWrites = [];
|
|
1346
|
-
// Whether end() has been called but writable not yet closed
|
|
1347
|
-
this._endPending = false;
|
|
1348
|
-
// Track if we've already set up data forwarding
|
|
1349
1531
|
this._dataForwardingSetup = false;
|
|
1350
|
-
|
|
1351
|
-
this.
|
|
1352
|
-
|
|
1353
|
-
this._readableConsuming = false;
|
|
1354
|
-
/** @internal - buffer for auto-consumed data */
|
|
1355
|
-
this._autoConsumedBuffer = [];
|
|
1356
|
-
this._autoConsumedBufferIndex = 0;
|
|
1357
|
-
/** @internal - whether auto-consume has ended */
|
|
1358
|
-
this._autoConsumeEnded = false;
|
|
1359
|
-
/** @internal - promise that resolves when auto-consume finishes */
|
|
1360
|
-
this._autoConsumePromise = null;
|
|
1532
|
+
this._endTimer = null;
|
|
1533
|
+
this._webStream = null;
|
|
1534
|
+
this._sideForwardingCleanup = null;
|
|
1361
1535
|
this.objectMode = options?.objectMode ?? false;
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1536
|
+
this._transformImpl = options?.transform;
|
|
1537
|
+
this._flushImpl = options?.flush;
|
|
1538
|
+
this._readable = new Readable({
|
|
1539
|
+
objectMode: this.objectMode
|
|
1540
|
+
});
|
|
1541
|
+
this._writable = new Writable({
|
|
1542
|
+
objectMode: this.objectMode,
|
|
1543
|
+
write: (chunk, _encoding, callback) => {
|
|
1544
|
+
this._runTransform(chunk)
|
|
1545
|
+
.then(() => callback(null))
|
|
1546
|
+
.catch(err => callback(err));
|
|
1547
|
+
},
|
|
1548
|
+
final: callback => {
|
|
1549
|
+
this._runFlush()
|
|
1550
|
+
.then(() => {
|
|
1551
|
+
this._readable.push(null);
|
|
1552
|
+
callback(null);
|
|
1553
|
+
})
|
|
1554
|
+
.catch(err => callback(err));
|
|
1377
1555
|
}
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1556
|
+
});
|
|
1557
|
+
this._setupSideForwarding();
|
|
1558
|
+
}
|
|
1559
|
+
_setupSideForwarding() {
|
|
1560
|
+
if (this._sideForwardingCleanup) {
|
|
1561
|
+
this._sideForwardingCleanup();
|
|
1562
|
+
this._sideForwardingCleanup = null;
|
|
1563
|
+
}
|
|
1564
|
+
const registry = createListenerRegistry();
|
|
1565
|
+
registry.once(this._readable, "end", () => this.emit("end"));
|
|
1566
|
+
registry.add(this._readable, "error", err => this._emitErrorOnce(err));
|
|
1567
|
+
registry.once(this._writable, "finish", () => this.emit("finish"));
|
|
1568
|
+
registry.add(this._writable, "drain", () => this.emit("drain"));
|
|
1569
|
+
registry.add(this._writable, "error", err => this._emitErrorOnce(err));
|
|
1570
|
+
this._sideForwardingCleanup = () => registry.cleanup();
|
|
1571
|
+
}
|
|
1572
|
+
_scheduleEnd() {
|
|
1573
|
+
if (this._destroyed || this._errored) {
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1576
|
+
if (this._writable.writableEnded) {
|
|
1577
|
+
return;
|
|
1578
|
+
}
|
|
1579
|
+
if (this._endTimer) {
|
|
1580
|
+
clearTimeout(this._endTimer);
|
|
1581
|
+
}
|
|
1582
|
+
// Defer closing to allow writes triggered during 'data' callbacks.
|
|
1583
|
+
this._endTimer = setTimeout(() => {
|
|
1584
|
+
this._endTimer = null;
|
|
1585
|
+
if (this._destroyed || this._errored || this._writable.writableEnded) {
|
|
1586
|
+
return;
|
|
1387
1587
|
}
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1588
|
+
this._writable.end();
|
|
1589
|
+
}, 0);
|
|
1590
|
+
}
|
|
1591
|
+
_emitErrorOnce(err) {
|
|
1592
|
+
if (this._errored) {
|
|
1593
|
+
return;
|
|
1594
|
+
}
|
|
1595
|
+
this._errored = true;
|
|
1596
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
1597
|
+
this.emit("error", error);
|
|
1598
|
+
if (!this._destroyed) {
|
|
1599
|
+
this._destroyed = true;
|
|
1600
|
+
this._readable.destroy(error);
|
|
1601
|
+
this._writable.destroy(error);
|
|
1602
|
+
queueMicrotask(() => this.emit("close"));
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
_hasSubclassTransform() {
|
|
1606
|
+
if (this._transformImpl) {
|
|
1607
|
+
return false;
|
|
1608
|
+
}
|
|
1609
|
+
const proto = Object.getPrototypeOf(this);
|
|
1610
|
+
return proto._transform !== Transform.prototype._transform;
|
|
1611
|
+
}
|
|
1612
|
+
_hasSubclassFlush() {
|
|
1613
|
+
if (this._flushImpl) {
|
|
1614
|
+
return false;
|
|
1615
|
+
}
|
|
1616
|
+
const proto = Object.getPrototypeOf(this);
|
|
1617
|
+
return proto._flush !== Transform.prototype._flush;
|
|
1618
|
+
}
|
|
1619
|
+
async _runTransform(chunk) {
|
|
1620
|
+
if (this._destroyed || this._errored) {
|
|
1621
|
+
throw new Error(this._errored ? "Cannot write after stream errored" : "Cannot write after stream destroyed");
|
|
1622
|
+
}
|
|
1623
|
+
try {
|
|
1624
|
+
if (this._hasSubclassTransform()) {
|
|
1625
|
+
await new Promise((resolve, reject) => {
|
|
1626
|
+
this._transform(chunk, "utf8", (err, data) => {
|
|
1627
|
+
if (err) {
|
|
1628
|
+
reject(err);
|
|
1629
|
+
return;
|
|
1630
|
+
}
|
|
1631
|
+
if (data !== undefined) {
|
|
1632
|
+
this.push(data);
|
|
1633
|
+
}
|
|
1634
|
+
resolve();
|
|
1635
|
+
});
|
|
1636
|
+
});
|
|
1637
|
+
return;
|
|
1395
1638
|
}
|
|
1396
|
-
const
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
// Check for subclass _transform override first
|
|
1409
|
-
if (hasSubclassTransform()) {
|
|
1410
|
-
// Call subclass _transform method (Node.js style)
|
|
1411
|
-
// _transform signature is (chunk, encoding, callback)
|
|
1412
|
-
await new Promise((resolve, reject) => {
|
|
1413
|
-
this._transform(chunk, "utf8", (err, data) => {
|
|
1414
|
-
if (err) {
|
|
1415
|
-
reject(err);
|
|
1416
|
-
}
|
|
1417
|
-
else {
|
|
1418
|
-
if (data !== undefined) {
|
|
1419
|
-
controller.enqueue(data);
|
|
1420
|
-
}
|
|
1421
|
-
resolve();
|
|
1422
|
-
}
|
|
1423
|
-
});
|
|
1424
|
-
});
|
|
1425
|
-
}
|
|
1426
|
-
else if (userTransform) {
|
|
1427
|
-
const transformParamCount = userTransform.length;
|
|
1428
|
-
if (transformParamCount >= 3) {
|
|
1429
|
-
// Node.js style: transform(chunk, encoding, callback)
|
|
1430
|
-
await new Promise((resolve, reject) => {
|
|
1431
|
-
userTransform.call(getInstance(), chunk, "utf8", (err, data) => {
|
|
1432
|
-
if (err) {
|
|
1433
|
-
reject(err);
|
|
1434
|
-
}
|
|
1435
|
-
else {
|
|
1436
|
-
if (data !== undefined) {
|
|
1437
|
-
controller.enqueue(data);
|
|
1438
|
-
}
|
|
1439
|
-
resolve();
|
|
1440
|
-
}
|
|
1441
|
-
});
|
|
1442
|
-
});
|
|
1639
|
+
const userTransform = this._transformImpl;
|
|
1640
|
+
if (!userTransform) {
|
|
1641
|
+
this.push(chunk);
|
|
1642
|
+
return;
|
|
1643
|
+
}
|
|
1644
|
+
const paramCount = userTransform.length;
|
|
1645
|
+
if (paramCount >= 3) {
|
|
1646
|
+
await new Promise((resolve, reject) => {
|
|
1647
|
+
userTransform.call(this, chunk, "utf8", (err, data) => {
|
|
1648
|
+
if (err) {
|
|
1649
|
+
reject(err);
|
|
1650
|
+
return;
|
|
1443
1651
|
}
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
userTransform.call(getInstance(), chunk, (err, data) => {
|
|
1447
|
-
if (err) {
|
|
1448
|
-
reject(err);
|
|
1449
|
-
}
|
|
1450
|
-
else {
|
|
1451
|
-
if (data !== undefined) {
|
|
1452
|
-
controller.enqueue(data);
|
|
1453
|
-
}
|
|
1454
|
-
resolve();
|
|
1455
|
-
}
|
|
1456
|
-
});
|
|
1457
|
-
});
|
|
1652
|
+
if (data !== undefined) {
|
|
1653
|
+
this.push(data);
|
|
1458
1654
|
}
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
controller.enqueue(result);
|
|
1471
|
-
}
|
|
1472
|
-
}
|
|
1655
|
+
resolve();
|
|
1656
|
+
});
|
|
1657
|
+
});
|
|
1658
|
+
return;
|
|
1659
|
+
}
|
|
1660
|
+
if (paramCount === 2) {
|
|
1661
|
+
await new Promise((resolve, reject) => {
|
|
1662
|
+
userTransform.call(this, chunk, (err, data) => {
|
|
1663
|
+
if (err) {
|
|
1664
|
+
reject(err);
|
|
1665
|
+
return;
|
|
1473
1666
|
}
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
}
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1667
|
+
if (data !== undefined) {
|
|
1668
|
+
this.push(data);
|
|
1669
|
+
}
|
|
1670
|
+
resolve();
|
|
1671
|
+
});
|
|
1672
|
+
});
|
|
1673
|
+
return;
|
|
1674
|
+
}
|
|
1675
|
+
const result = userTransform.call(this, chunk);
|
|
1676
|
+
if (result && typeof result.then === "function") {
|
|
1677
|
+
const awaited = await result;
|
|
1678
|
+
if (awaited !== undefined) {
|
|
1679
|
+
this.push(awaited);
|
|
1486
1680
|
}
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
if (flushParamCount >= 1) {
|
|
1510
|
-
// Node.js style: flush(callback)
|
|
1511
|
-
await new Promise((resolve, reject) => {
|
|
1512
|
-
userFlush.call(getInstance(), (err, data) => {
|
|
1513
|
-
if (err) {
|
|
1514
|
-
reject(err);
|
|
1515
|
-
}
|
|
1516
|
-
else {
|
|
1517
|
-
if (data !== undefined) {
|
|
1518
|
-
controller.enqueue(data);
|
|
1519
|
-
}
|
|
1520
|
-
resolve();
|
|
1521
|
-
}
|
|
1522
|
-
});
|
|
1523
|
-
});
|
|
1681
|
+
return;
|
|
1682
|
+
}
|
|
1683
|
+
if (result !== undefined) {
|
|
1684
|
+
this.push(result);
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
catch (err) {
|
|
1688
|
+
this._emitErrorOnce(err);
|
|
1689
|
+
throw err;
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
async _runFlush() {
|
|
1693
|
+
if (this._destroyed || this._errored) {
|
|
1694
|
+
return;
|
|
1695
|
+
}
|
|
1696
|
+
try {
|
|
1697
|
+
if (this._hasSubclassFlush()) {
|
|
1698
|
+
await new Promise((resolve, reject) => {
|
|
1699
|
+
this._flush((err, data) => {
|
|
1700
|
+
if (err) {
|
|
1701
|
+
reject(err);
|
|
1702
|
+
return;
|
|
1524
1703
|
}
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
const result = userFlush.call(getInstance());
|
|
1528
|
-
if (result && typeof result.then === "function") {
|
|
1529
|
-
const awaitedResult = await result;
|
|
1530
|
-
if (awaitedResult !== undefined && awaitedResult !== null) {
|
|
1531
|
-
controller.enqueue(awaitedResult);
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
1534
|
-
else {
|
|
1535
|
-
if (result !== undefined && result !== null) {
|
|
1536
|
-
controller.enqueue(result);
|
|
1537
|
-
}
|
|
1538
|
-
}
|
|
1704
|
+
if (data !== undefined) {
|
|
1705
|
+
this.push(data);
|
|
1539
1706
|
}
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
}
|
|
1543
|
-
|
|
1544
|
-
controller.error(err);
|
|
1545
|
-
emitEvent("error", err);
|
|
1546
|
-
}
|
|
1547
|
-
finally {
|
|
1548
|
-
setController(null);
|
|
1549
|
-
}
|
|
1707
|
+
resolve();
|
|
1708
|
+
});
|
|
1709
|
+
});
|
|
1710
|
+
return;
|
|
1550
1711
|
}
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
objectMode: this.objectMode
|
|
1555
|
-
});
|
|
1556
|
-
this._writable = new Writable({
|
|
1557
|
-
stream: this._stream.writable,
|
|
1558
|
-
objectMode: this.objectMode
|
|
1559
|
-
});
|
|
1560
|
-
// Forward non-data events (data forwarding is lazy to avoid premature flowing)
|
|
1561
|
-
this._readable.on("end", () => this.emit("end"));
|
|
1562
|
-
// Only forward errors if not already errored (to prevent duplicate events)
|
|
1563
|
-
this._readable.on("error", err => {
|
|
1564
|
-
if (!this._errored) {
|
|
1565
|
-
this._errored = true;
|
|
1566
|
-
this.emit("error", err);
|
|
1712
|
+
const userFlush = this._flushImpl;
|
|
1713
|
+
if (!userFlush) {
|
|
1714
|
+
return;
|
|
1567
1715
|
}
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1716
|
+
const paramCount = userFlush.length;
|
|
1717
|
+
if (paramCount >= 1) {
|
|
1718
|
+
await new Promise((resolve, reject) => {
|
|
1719
|
+
userFlush.call(this, (err, data) => {
|
|
1720
|
+
if (err) {
|
|
1721
|
+
reject(err);
|
|
1722
|
+
return;
|
|
1723
|
+
}
|
|
1724
|
+
if (data !== undefined) {
|
|
1725
|
+
this.push(data);
|
|
1726
|
+
}
|
|
1727
|
+
resolve();
|
|
1728
|
+
});
|
|
1729
|
+
});
|
|
1730
|
+
return;
|
|
1576
1731
|
}
|
|
1577
|
-
|
|
1732
|
+
const result = userFlush.call(this);
|
|
1733
|
+
if (result && typeof result.then === "function") {
|
|
1734
|
+
const awaited = await result;
|
|
1735
|
+
if (awaited !== undefined && awaited !== null) {
|
|
1736
|
+
this.push(awaited);
|
|
1737
|
+
}
|
|
1738
|
+
return;
|
|
1739
|
+
}
|
|
1740
|
+
if (result !== undefined && result !== null) {
|
|
1741
|
+
this.push(result);
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
catch (err) {
|
|
1745
|
+
this._emitErrorOnce(err);
|
|
1746
|
+
throw err;
|
|
1747
|
+
}
|
|
1578
1748
|
}
|
|
1579
1749
|
/**
|
|
1580
|
-
* Override on to
|
|
1750
|
+
* Override on() to lazily forward readable 'data' events.
|
|
1751
|
+
* Avoids starting flowing mode unless requested.
|
|
1581
1752
|
*/
|
|
1582
1753
|
on(event, listener) {
|
|
1583
|
-
// Set up data forwarding when first external data listener is added
|
|
1584
1754
|
if (event === "data" && !this._dataForwardingSetup) {
|
|
1585
1755
|
this._dataForwardingSetup = true;
|
|
1586
|
-
this._readable.on("data",
|
|
1587
|
-
}
|
|
1588
|
-
super.on(event, listener);
|
|
1589
|
-
// When data listener is added, mark as having consumer
|
|
1590
|
-
// and start the readable in flowing mode
|
|
1591
|
-
if (event === "data") {
|
|
1592
|
-
this._hasDataConsumer = true;
|
|
1593
|
-
this._readable.resume();
|
|
1756
|
+
this._readable.on("data", chunk => this.emit("data", chunk));
|
|
1594
1757
|
}
|
|
1595
|
-
return
|
|
1758
|
+
return super.on(event, listener);
|
|
1596
1759
|
}
|
|
1597
1760
|
write(chunk, encodingOrCallback, callback) {
|
|
1598
1761
|
const cb = typeof encodingOrCallback === "function" ? encodingOrCallback : callback;
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
cb?.(err);
|
|
1603
|
-
return false;
|
|
1604
|
-
}
|
|
1605
|
-
// Ensure readable is being consumed to allow transform to execute
|
|
1606
|
-
// This matches Node.js behavior where transform executes immediately on write
|
|
1607
|
-
// Only auto-consume if no explicit consumer (data listener or pipe)
|
|
1608
|
-
if (!this._readableConsuming && !this._hasDataConsumer) {
|
|
1609
|
-
this._readableConsuming = true;
|
|
1610
|
-
this._startAutoConsume();
|
|
1611
|
-
}
|
|
1612
|
-
// If end() was called but writable not yet closed, buffer the write
|
|
1613
|
-
// This allows writes during data event handlers to be processed
|
|
1614
|
-
if (this._endPending) {
|
|
1615
|
-
this._pendingEndWrites.push({ chunk, callback: cb });
|
|
1616
|
-
return true;
|
|
1762
|
+
// If end() has been requested, keep the close deferred as long as writes continue.
|
|
1763
|
+
if (this._ended && !this._writable.writableEnded) {
|
|
1764
|
+
this._scheduleEnd();
|
|
1617
1765
|
}
|
|
1618
1766
|
return this._writable.write(chunk, cb);
|
|
1619
1767
|
}
|
|
1620
|
-
/** @internal - auto-consume readable to allow transform to execute */
|
|
1621
|
-
_startAutoConsume() {
|
|
1622
|
-
this._autoConsumePromise = (async () => {
|
|
1623
|
-
try {
|
|
1624
|
-
for await (const chunk of this._readable) {
|
|
1625
|
-
// Buffer the data for later retrieval
|
|
1626
|
-
this._autoConsumedBuffer.push(chunk);
|
|
1627
|
-
// Also emit data event for listeners
|
|
1628
|
-
this.emit("data", chunk);
|
|
1629
|
-
}
|
|
1630
|
-
this._autoConsumeEnded = true;
|
|
1631
|
-
this.emit("end");
|
|
1632
|
-
}
|
|
1633
|
-
catch (err) {
|
|
1634
|
-
this.emit("error", err);
|
|
1635
|
-
}
|
|
1636
|
-
})();
|
|
1637
|
-
}
|
|
1638
1768
|
end(chunkOrCallback, encodingOrCallback, callback) {
|
|
1639
1769
|
if (this._ended) {
|
|
1640
1770
|
return this;
|
|
1641
1771
|
}
|
|
1642
1772
|
this._ended = true;
|
|
1643
|
-
this._endPending = true;
|
|
1644
1773
|
const chunk = typeof chunkOrCallback === "function" ? undefined : chunkOrCallback;
|
|
1645
1774
|
const cb = typeof chunkOrCallback === "function"
|
|
1646
1775
|
? chunkOrCallback
|
|
@@ -1653,18 +1782,7 @@ export class Transform extends EventEmitter {
|
|
|
1653
1782
|
if (chunk !== undefined) {
|
|
1654
1783
|
this._writable.write(chunk);
|
|
1655
1784
|
}
|
|
1656
|
-
|
|
1657
|
-
// processing and data events complete before we close the writable.
|
|
1658
|
-
// Microtasks run before the TransformStream processes data.
|
|
1659
|
-
setTimeout(() => {
|
|
1660
|
-
// Process any writes that occurred during data events
|
|
1661
|
-
for (const { chunk: pendingChunk, callback } of this._pendingEndWrites) {
|
|
1662
|
-
this._writable.write(pendingChunk, callback);
|
|
1663
|
-
}
|
|
1664
|
-
this._pendingEndWrites = [];
|
|
1665
|
-
this._endPending = false;
|
|
1666
|
-
this._writable.end();
|
|
1667
|
-
}, 0);
|
|
1785
|
+
this._scheduleEnd();
|
|
1668
1786
|
return this;
|
|
1669
1787
|
}
|
|
1670
1788
|
/**
|
|
@@ -1674,11 +1792,9 @@ export class Transform extends EventEmitter {
|
|
|
1674
1792
|
return this._readable.read(size);
|
|
1675
1793
|
}
|
|
1676
1794
|
/**
|
|
1677
|
-
* Pipe
|
|
1795
|
+
* Pipe readable side to destination
|
|
1678
1796
|
*/
|
|
1679
1797
|
pipe(destination) {
|
|
1680
|
-
// Mark as having consumer to prevent auto-consume conflict
|
|
1681
|
-
this._hasDataConsumer = true;
|
|
1682
1798
|
return this._readable.pipe(destination);
|
|
1683
1799
|
}
|
|
1684
1800
|
/**
|
|
@@ -1716,6 +1832,10 @@ export class Transform extends EventEmitter {
|
|
|
1716
1832
|
return;
|
|
1717
1833
|
}
|
|
1718
1834
|
this._destroyed = true;
|
|
1835
|
+
if (this._sideForwardingCleanup) {
|
|
1836
|
+
this._sideForwardingCleanup();
|
|
1837
|
+
this._sideForwardingCleanup = null;
|
|
1838
|
+
}
|
|
1719
1839
|
this._readable.destroy(error);
|
|
1720
1840
|
this._writable.destroy(error);
|
|
1721
1841
|
queueMicrotask(() => this.emit("close"));
|
|
@@ -1724,7 +1844,44 @@ export class Transform extends EventEmitter {
|
|
|
1724
1844
|
* Get the underlying Web TransformStream
|
|
1725
1845
|
*/
|
|
1726
1846
|
get webStream() {
|
|
1727
|
-
|
|
1847
|
+
if (this._webStream) {
|
|
1848
|
+
return this._webStream;
|
|
1849
|
+
}
|
|
1850
|
+
// Web Streams interop layer.
|
|
1851
|
+
const iterator = this[Symbol.asyncIterator]();
|
|
1852
|
+
const readable = new ReadableStream({
|
|
1853
|
+
pull: async (controller) => {
|
|
1854
|
+
const { done, value } = await iterator.next();
|
|
1855
|
+
if (done) {
|
|
1856
|
+
controller.close();
|
|
1857
|
+
return;
|
|
1858
|
+
}
|
|
1859
|
+
controller.enqueue(value);
|
|
1860
|
+
},
|
|
1861
|
+
cancel: reason => {
|
|
1862
|
+
this.destroy(reason instanceof Error ? reason : new Error(String(reason)));
|
|
1863
|
+
}
|
|
1864
|
+
});
|
|
1865
|
+
const writable = new WritableStream({
|
|
1866
|
+
write: chunk => new Promise((resolve, reject) => {
|
|
1867
|
+
this.write(chunk, err => {
|
|
1868
|
+
if (err) {
|
|
1869
|
+
reject(err);
|
|
1870
|
+
}
|
|
1871
|
+
else {
|
|
1872
|
+
resolve();
|
|
1873
|
+
}
|
|
1874
|
+
});
|
|
1875
|
+
}),
|
|
1876
|
+
close: () => new Promise(resolve => {
|
|
1877
|
+
this.end(() => resolve());
|
|
1878
|
+
}),
|
|
1879
|
+
abort: reason => {
|
|
1880
|
+
this.destroy(reason instanceof Error ? reason : new Error(String(reason)));
|
|
1881
|
+
}
|
|
1882
|
+
});
|
|
1883
|
+
this._webStream = { readable, writable };
|
|
1884
|
+
return this._webStream;
|
|
1728
1885
|
}
|
|
1729
1886
|
get readable() {
|
|
1730
1887
|
return this._readable.readable;
|
|
@@ -1766,19 +1923,6 @@ export class Transform extends EventEmitter {
|
|
|
1766
1923
|
* Async iterator support
|
|
1767
1924
|
*/
|
|
1768
1925
|
async *[Symbol.asyncIterator]() {
|
|
1769
|
-
// If auto-consume is running, wait for it to finish and use its buffer
|
|
1770
|
-
if (this._autoConsumePromise) {
|
|
1771
|
-
await this._autoConsumePromise;
|
|
1772
|
-
// Yield all buffered data
|
|
1773
|
-
while (this._autoConsumedBufferIndex < this._autoConsumedBuffer.length) {
|
|
1774
|
-
yield this._autoConsumedBuffer[this._autoConsumedBufferIndex++];
|
|
1775
|
-
}
|
|
1776
|
-
// Reset when drained to avoid prefix growth
|
|
1777
|
-
this._autoConsumedBuffer.length = 0;
|
|
1778
|
-
this._autoConsumedBufferIndex = 0;
|
|
1779
|
-
return;
|
|
1780
|
-
}
|
|
1781
|
-
// Otherwise delegate to readable's iterator
|
|
1782
1926
|
yield* this._readable[Symbol.asyncIterator]();
|
|
1783
1927
|
}
|
|
1784
1928
|
// =========================================================================
|
|
@@ -1789,23 +1933,18 @@ export class Transform extends EventEmitter {
|
|
|
1789
1933
|
*/
|
|
1790
1934
|
static fromWeb(webStream, options) {
|
|
1791
1935
|
const transform = new Transform(options);
|
|
1792
|
-
|
|
1793
|
-
transform._stream = webStream;
|
|
1936
|
+
transform._webStream = webStream;
|
|
1794
1937
|
// Replace internal streams with the ones from the web stream
|
|
1795
1938
|
const newReadable = Readable.fromWeb(webStream.readable, { objectMode: options?.objectMode });
|
|
1796
1939
|
const newWritable = Writable.fromWeb(webStream.writable, { objectMode: options?.objectMode });
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1940
|
+
if (transform._sideForwardingCleanup) {
|
|
1941
|
+
transform._sideForwardingCleanup();
|
|
1942
|
+
transform._sideForwardingCleanup = null;
|
|
1943
|
+
}
|
|
1800
1944
|
transform._readable = newReadable;
|
|
1801
1945
|
transform._writable = newWritable;
|
|
1802
|
-
// Re-connect event forwarding
|
|
1803
|
-
|
|
1804
|
-
newReadable.on("end", () => transform.emit("end"));
|
|
1805
|
-
newReadable.on("error", (err) => transform.emit("error", err));
|
|
1806
|
-
newWritable.on("finish", () => transform.emit("finish"));
|
|
1807
|
-
newWritable.on("drain", () => transform.emit("drain"));
|
|
1808
|
-
newWritable.on("error", (err) => transform.emit("error", err));
|
|
1946
|
+
// Re-connect event forwarding (data forwarding remains lazy via Transform.on)
|
|
1947
|
+
transform._setupSideForwarding();
|
|
1809
1948
|
return transform;
|
|
1810
1949
|
}
|
|
1811
1950
|
/**
|
|
@@ -1861,7 +2000,13 @@ export class Duplex extends EventEmitter {
|
|
|
1861
2000
|
callback();
|
|
1862
2001
|
}
|
|
1863
2002
|
});
|
|
1864
|
-
|
|
2003
|
+
const onError = (err) => {
|
|
2004
|
+
duplex.emit("error", err);
|
|
2005
|
+
};
|
|
2006
|
+
const cleanupError = addEmitterListener(readable, "error", onError);
|
|
2007
|
+
addEmitterListener(readable, "end", cleanupError, { once: true });
|
|
2008
|
+
addEmitterListener(readable, "close", cleanupError, { once: true });
|
|
2009
|
+
addEmitterListener(sink, "finish", cleanupError, { once: true });
|
|
1865
2010
|
readable.pipe(sink);
|
|
1866
2011
|
};
|
|
1867
2012
|
// If it has readable and/or writable properties
|
|
@@ -1869,21 +2014,25 @@ export class Duplex extends EventEmitter {
|
|
|
1869
2014
|
source !== null &&
|
|
1870
2015
|
"readable" in source &&
|
|
1871
2016
|
"writable" in source) {
|
|
1872
|
-
const duplex = new Duplex();
|
|
1873
2017
|
const pair = source;
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
2018
|
+
// Create one duplex that can bridge both sides.
|
|
2019
|
+
// (Previous behavior returned a new writable-only Duplex and dropped the readable side.)
|
|
2020
|
+
const duplex = new Duplex({
|
|
2021
|
+
readableObjectMode: pair.readable?.readableObjectMode,
|
|
2022
|
+
writableObjectMode: pair.writable?.writableObjectMode,
|
|
2023
|
+
write: pair.writable
|
|
2024
|
+
? (chunk, encoding, callback) => {
|
|
1881
2025
|
pair.writable.write(chunk, encoding, callback);
|
|
1882
|
-
}
|
|
1883
|
-
|
|
2026
|
+
}
|
|
2027
|
+
: undefined,
|
|
2028
|
+
final: pair.writable
|
|
2029
|
+
? callback => {
|
|
1884
2030
|
pair.writable.end(callback);
|
|
1885
2031
|
}
|
|
1886
|
-
|
|
2032
|
+
: undefined
|
|
2033
|
+
});
|
|
2034
|
+
if (pair.readable) {
|
|
2035
|
+
forwardReadableToDuplex(pair.readable, duplex);
|
|
1887
2036
|
}
|
|
1888
2037
|
return duplex;
|
|
1889
2038
|
}
|
|
@@ -1921,9 +2070,22 @@ export class Duplex extends EventEmitter {
|
|
|
1921
2070
|
*/
|
|
1922
2071
|
static fromWeb(pair, options) {
|
|
1923
2072
|
const duplex = new Duplex(options);
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
2073
|
+
const newReadable = new Readable({
|
|
2074
|
+
stream: pair.readable,
|
|
2075
|
+
objectMode: duplex.readableObjectMode
|
|
2076
|
+
});
|
|
2077
|
+
const newWritable = new Writable({
|
|
2078
|
+
stream: pair.writable,
|
|
2079
|
+
objectMode: duplex.writableObjectMode
|
|
2080
|
+
});
|
|
2081
|
+
if (duplex._sideForwardingCleanup) {
|
|
2082
|
+
duplex._sideForwardingCleanup();
|
|
2083
|
+
duplex._sideForwardingCleanup = null;
|
|
2084
|
+
}
|
|
2085
|
+
duplex._readable = newReadable;
|
|
2086
|
+
duplex._writable = newWritable;
|
|
2087
|
+
// Re-wire event forwarding (data forwarding remains lazy via Duplex.on)
|
|
2088
|
+
duplex._setupSideForwarding();
|
|
1927
2089
|
return duplex;
|
|
1928
2090
|
}
|
|
1929
2091
|
/**
|
|
@@ -1939,6 +2101,7 @@ export class Duplex extends EventEmitter {
|
|
|
1939
2101
|
super();
|
|
1940
2102
|
// Track if we've already set up data forwarding
|
|
1941
2103
|
this._dataForwardingSetup = false;
|
|
2104
|
+
this._sideForwardingCleanup = null;
|
|
1942
2105
|
this.allowHalfOpen = options?.allowHalfOpen ?? true;
|
|
1943
2106
|
// Support shorthand objectMode option
|
|
1944
2107
|
const objectMode = options?.objectMode ?? false;
|
|
@@ -1955,23 +2118,31 @@ export class Duplex extends EventEmitter {
|
|
|
1955
2118
|
write: options?.write?.bind(this),
|
|
1956
2119
|
final: options?.final?.bind(this)
|
|
1957
2120
|
});
|
|
2121
|
+
this._setupSideForwarding();
|
|
2122
|
+
}
|
|
2123
|
+
_setupSideForwarding() {
|
|
2124
|
+
if (this._sideForwardingCleanup) {
|
|
2125
|
+
this._sideForwardingCleanup();
|
|
2126
|
+
this._sideForwardingCleanup = null;
|
|
2127
|
+
}
|
|
2128
|
+
const registry = createListenerRegistry();
|
|
1958
2129
|
// Forward non-data events (data forwarding is lazy to avoid premature flowing)
|
|
1959
|
-
this._readable
|
|
2130
|
+
registry.once(this._readable, "end", () => {
|
|
1960
2131
|
this.emit("end");
|
|
1961
|
-
// If not allowHalfOpen, end the writable side too
|
|
1962
2132
|
if (!this.allowHalfOpen) {
|
|
1963
2133
|
this._writable.end();
|
|
1964
2134
|
}
|
|
1965
2135
|
});
|
|
1966
|
-
this._readable
|
|
1967
|
-
this._writable
|
|
1968
|
-
this._writable
|
|
1969
|
-
this._writable
|
|
1970
|
-
|
|
2136
|
+
registry.add(this._readable, "error", err => this.emit("error", err));
|
|
2137
|
+
registry.add(this._writable, "error", err => this.emit("error", err));
|
|
2138
|
+
registry.once(this._writable, "finish", () => this.emit("finish"));
|
|
2139
|
+
registry.add(this._writable, "drain", () => this.emit("drain"));
|
|
2140
|
+
registry.once(this._writable, "close", () => {
|
|
1971
2141
|
if (!this.allowHalfOpen && !this._readable.destroyed) {
|
|
1972
2142
|
this._readable.destroy();
|
|
1973
2143
|
}
|
|
1974
2144
|
});
|
|
2145
|
+
this._sideForwardingCleanup = () => registry.cleanup();
|
|
1975
2146
|
}
|
|
1976
2147
|
/**
|
|
1977
2148
|
* Override on() to set up data forwarding lazily
|
|
@@ -2090,6 +2261,10 @@ export class Duplex extends EventEmitter {
|
|
|
2090
2261
|
* Destroy both sides
|
|
2091
2262
|
*/
|
|
2092
2263
|
destroy(error) {
|
|
2264
|
+
if (this._sideForwardingCleanup) {
|
|
2265
|
+
this._sideForwardingCleanup();
|
|
2266
|
+
this._sideForwardingCleanup = null;
|
|
2267
|
+
}
|
|
2093
2268
|
this._readable.destroy(error);
|
|
2094
2269
|
this._writable.destroy(error);
|
|
2095
2270
|
return this;
|
|
@@ -2252,36 +2427,16 @@ export class BufferedStream extends StandaloneBufferedStream {
|
|
|
2252
2427
|
* Create a readable stream with custom read implementation
|
|
2253
2428
|
*/
|
|
2254
2429
|
export function createReadable(options) {
|
|
2255
|
-
|
|
2256
|
-
//
|
|
2257
|
-
|
|
2258
|
-
const originalRead = readable.read.bind(readable);
|
|
2259
|
-
readable.read = function (size) {
|
|
2260
|
-
options.read(size ?? 16384);
|
|
2261
|
-
return originalRead(size);
|
|
2262
|
-
};
|
|
2263
|
-
}
|
|
2264
|
-
return readable;
|
|
2430
|
+
// Readable already supports Node-style `read()` via the constructor option.
|
|
2431
|
+
// Keep this helper minimal to avoid accidental double-read behavior.
|
|
2432
|
+
return new Readable(options);
|
|
2265
2433
|
}
|
|
2266
2434
|
/**
|
|
2267
2435
|
* Create a readable stream from an async iterable
|
|
2268
2436
|
*/
|
|
2269
2437
|
export function createReadableFromAsyncIterable(iterable, options) {
|
|
2270
2438
|
const readable = new Readable({ ...options, objectMode: options?.objectMode ?? true });
|
|
2271
|
-
(
|
|
2272
|
-
try {
|
|
2273
|
-
for await (const chunk of iterable) {
|
|
2274
|
-
if (!readable.push(chunk)) {
|
|
2275
|
-
// Backpressure - wait a bit
|
|
2276
|
-
await new Promise(resolve => setTimeout(resolve, 0));
|
|
2277
|
-
}
|
|
2278
|
-
}
|
|
2279
|
-
readable.push(null);
|
|
2280
|
-
}
|
|
2281
|
-
catch (err) {
|
|
2282
|
-
readable.destroy(err);
|
|
2283
|
-
}
|
|
2284
|
-
})();
|
|
2439
|
+
pumpAsyncIterableToReadable(readable, iterable);
|
|
2285
2440
|
return readable;
|
|
2286
2441
|
}
|
|
2287
2442
|
/**
|
|
@@ -2310,38 +2465,8 @@ export function createReadableFromArray(data, options) {
|
|
|
2310
2465
|
* Create a writable stream with custom write implementation
|
|
2311
2466
|
*/
|
|
2312
2467
|
export function createWritable(options) {
|
|
2313
|
-
//
|
|
2314
|
-
|
|
2315
|
-
write: async (chunk) => {
|
|
2316
|
-
if (options?.write) {
|
|
2317
|
-
return new Promise((resolve, reject) => {
|
|
2318
|
-
options.write(chunk, "utf8", err => {
|
|
2319
|
-
if (err) {
|
|
2320
|
-
reject(err);
|
|
2321
|
-
}
|
|
2322
|
-
else {
|
|
2323
|
-
resolve();
|
|
2324
|
-
}
|
|
2325
|
-
});
|
|
2326
|
-
});
|
|
2327
|
-
}
|
|
2328
|
-
},
|
|
2329
|
-
close: async () => {
|
|
2330
|
-
if (options?.final) {
|
|
2331
|
-
return new Promise((resolve, reject) => {
|
|
2332
|
-
options.final(err => {
|
|
2333
|
-
if (err) {
|
|
2334
|
-
reject(err);
|
|
2335
|
-
}
|
|
2336
|
-
else {
|
|
2337
|
-
resolve();
|
|
2338
|
-
}
|
|
2339
|
-
});
|
|
2340
|
-
});
|
|
2341
|
-
}
|
|
2342
|
-
}
|
|
2343
|
-
});
|
|
2344
|
-
return new Writable({ ...options, stream });
|
|
2468
|
+
// Writable already supports Node-style `write()` / `final()` via the constructor.
|
|
2469
|
+
return new Writable(options);
|
|
2345
2470
|
}
|
|
2346
2471
|
/**
|
|
2347
2472
|
* Create a transform stream from a transform function
|
|
@@ -2476,11 +2601,17 @@ export function pipeline(...args) {
|
|
|
2476
2601
|
const transforms = normalized.slice(1, -1);
|
|
2477
2602
|
let completed = false;
|
|
2478
2603
|
const allStreams = [source, ...transforms, destination];
|
|
2479
|
-
const
|
|
2604
|
+
const registry = createListenerRegistry();
|
|
2605
|
+
let onAbort;
|
|
2606
|
+
const cleanupWithSignal = (error) => {
|
|
2480
2607
|
if (completed) {
|
|
2481
2608
|
return;
|
|
2482
2609
|
}
|
|
2483
2610
|
completed = true;
|
|
2611
|
+
registry.cleanup();
|
|
2612
|
+
if (onAbort && options.signal) {
|
|
2613
|
+
options.signal.removeEventListener("abort", onAbort);
|
|
2614
|
+
}
|
|
2484
2615
|
// Destroy all streams on error
|
|
2485
2616
|
if (error) {
|
|
2486
2617
|
for (const stream of allStreams) {
|
|
@@ -2497,12 +2628,11 @@ export function pipeline(...args) {
|
|
|
2497
2628
|
// Handle abort signal
|
|
2498
2629
|
if (options.signal) {
|
|
2499
2630
|
if (options.signal.aborted) {
|
|
2500
|
-
|
|
2631
|
+
cleanupWithSignal(new Error("Pipeline aborted"));
|
|
2501
2632
|
return;
|
|
2502
2633
|
}
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
});
|
|
2634
|
+
onAbort = () => cleanupWithSignal(new Error("Pipeline aborted"));
|
|
2635
|
+
options.signal.addEventListener("abort", onAbort);
|
|
2506
2636
|
}
|
|
2507
2637
|
// Chain the streams
|
|
2508
2638
|
let current = source;
|
|
@@ -2516,13 +2646,35 @@ export function pipeline(...args) {
|
|
|
2516
2646
|
}
|
|
2517
2647
|
else {
|
|
2518
2648
|
// Don't end destination
|
|
2519
|
-
|
|
2649
|
+
let paused = false;
|
|
2650
|
+
let waitingForDrain = false;
|
|
2651
|
+
const onDrain = () => {
|
|
2652
|
+
waitingForDrain = false;
|
|
2653
|
+
if (paused && typeof current.resume === "function") {
|
|
2654
|
+
paused = false;
|
|
2655
|
+
current.resume();
|
|
2656
|
+
}
|
|
2657
|
+
};
|
|
2658
|
+
const onData = (chunk) => {
|
|
2659
|
+
const ok = destination.write(chunk);
|
|
2660
|
+
if (!ok && !waitingForDrain) {
|
|
2661
|
+
waitingForDrain = true;
|
|
2662
|
+
if (!paused && typeof current.pause === "function") {
|
|
2663
|
+
paused = true;
|
|
2664
|
+
current.pause();
|
|
2665
|
+
}
|
|
2666
|
+
registry.once(destination, "drain", onDrain);
|
|
2667
|
+
}
|
|
2668
|
+
};
|
|
2669
|
+
const onEnd = () => cleanupWithSignal();
|
|
2670
|
+
registry.add(current, "data", onData);
|
|
2671
|
+
registry.once(current, "end", onEnd);
|
|
2520
2672
|
}
|
|
2521
2673
|
// Handle completion
|
|
2522
|
-
|
|
2674
|
+
registry.once(destination, "finish", () => cleanupWithSignal());
|
|
2523
2675
|
// Handle errors on all streams
|
|
2524
2676
|
for (const stream of allStreams) {
|
|
2525
|
-
|
|
2677
|
+
registry.once(stream, "error", (err) => cleanupWithSignal(err));
|
|
2526
2678
|
}
|
|
2527
2679
|
});
|
|
2528
2680
|
// If callback provided, use it
|
|
@@ -2562,11 +2714,20 @@ export function finished(stream, optionsOrCallback, callback) {
|
|
|
2562
2714
|
const promise = new Promise((resolve, reject) => {
|
|
2563
2715
|
const normalizedStream = toBrowserPipelineStream(stream);
|
|
2564
2716
|
let resolved = false;
|
|
2717
|
+
const registry = createListenerRegistry();
|
|
2718
|
+
let onAbort;
|
|
2719
|
+
const cleanup = () => {
|
|
2720
|
+
registry.cleanup();
|
|
2721
|
+
if (onAbort && options.signal) {
|
|
2722
|
+
options.signal.removeEventListener("abort", onAbort);
|
|
2723
|
+
}
|
|
2724
|
+
};
|
|
2565
2725
|
const done = (err) => {
|
|
2566
2726
|
if (resolved) {
|
|
2567
2727
|
return;
|
|
2568
2728
|
}
|
|
2569
2729
|
resolved = true;
|
|
2730
|
+
cleanup();
|
|
2570
2731
|
if (err && !options.error) {
|
|
2571
2732
|
reject(err);
|
|
2572
2733
|
}
|
|
@@ -2580,9 +2741,8 @@ export function finished(stream, optionsOrCallback, callback) {
|
|
|
2580
2741
|
done(new Error("Aborted"));
|
|
2581
2742
|
return;
|
|
2582
2743
|
}
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
});
|
|
2744
|
+
onAbort = () => done(new Error("Aborted"));
|
|
2745
|
+
options.signal.addEventListener("abort", onAbort);
|
|
2586
2746
|
}
|
|
2587
2747
|
const checkReadable = options.readable !== false;
|
|
2588
2748
|
const checkWritable = options.writable !== false;
|
|
@@ -2597,13 +2757,13 @@ export function finished(stream, optionsOrCallback, callback) {
|
|
|
2597
2757
|
}
|
|
2598
2758
|
// Listen for events
|
|
2599
2759
|
if (checkWritable) {
|
|
2600
|
-
|
|
2760
|
+
registry.once(normalizedStream, "finish", () => done());
|
|
2601
2761
|
}
|
|
2602
2762
|
if (checkReadable) {
|
|
2603
|
-
|
|
2763
|
+
registry.once(normalizedStream, "end", () => done());
|
|
2604
2764
|
}
|
|
2605
|
-
|
|
2606
|
-
|
|
2765
|
+
registry.once(normalizedStream, "error", (err) => done(err));
|
|
2766
|
+
registry.once(normalizedStream, "close", () => done());
|
|
2607
2767
|
});
|
|
2608
2768
|
// If callback provided, use it
|
|
2609
2769
|
if (cb) {
|
|
@@ -2625,38 +2785,8 @@ export async function streamToPromise(stream) {
|
|
|
2625
2785
|
* (Browser equivalent of Node.js streamToBuffer)
|
|
2626
2786
|
*/
|
|
2627
2787
|
export async function streamToUint8Array(stream) {
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
iterable = Readable.fromWeb(stream);
|
|
2631
|
-
}
|
|
2632
|
-
else if (isAsyncIterable(stream)) {
|
|
2633
|
-
iterable = stream;
|
|
2634
|
-
}
|
|
2635
|
-
else {
|
|
2636
|
-
throw new Error("streamToUint8Array: unsupported stream type");
|
|
2637
|
-
}
|
|
2638
|
-
const chunks = [];
|
|
2639
|
-
let totalLength = 0;
|
|
2640
|
-
for await (const chunk of iterable) {
|
|
2641
|
-
chunks.push(chunk);
|
|
2642
|
-
totalLength += chunk.length;
|
|
2643
|
-
}
|
|
2644
|
-
// Fast paths
|
|
2645
|
-
const len = chunks.length;
|
|
2646
|
-
if (len === 0) {
|
|
2647
|
-
return new Uint8Array(0);
|
|
2648
|
-
}
|
|
2649
|
-
if (len === 1) {
|
|
2650
|
-
return chunks[0];
|
|
2651
|
-
}
|
|
2652
|
-
// Use precalculated total length
|
|
2653
|
-
const result = new Uint8Array(totalLength);
|
|
2654
|
-
let offset = 0;
|
|
2655
|
-
for (let i = 0; i < len; i++) {
|
|
2656
|
-
result.set(chunks[i], offset);
|
|
2657
|
-
offset += chunks[i].length;
|
|
2658
|
-
}
|
|
2659
|
-
return result;
|
|
2788
|
+
const { chunks, totalLength } = await collectStreamChunks(stream);
|
|
2789
|
+
return concatWithLength(chunks, totalLength);
|
|
2660
2790
|
}
|
|
2661
2791
|
/**
|
|
2662
2792
|
* Alias for streamToUint8Array (Node.js compatibility)
|
|
@@ -2666,8 +2796,10 @@ export const streamToBuffer = streamToUint8Array;
|
|
|
2666
2796
|
* Collect all data from a readable stream into a string
|
|
2667
2797
|
*/
|
|
2668
2798
|
export async function streamToString(stream, encoding) {
|
|
2669
|
-
const
|
|
2670
|
-
|
|
2799
|
+
const { chunks, totalLength } = await collectStreamChunks(stream);
|
|
2800
|
+
const combined = concatWithLength(chunks, totalLength);
|
|
2801
|
+
const decoder = encoding ? getTextDecoder(encoding) : textDecoder;
|
|
2802
|
+
return decoder.decode(combined);
|
|
2671
2803
|
}
|
|
2672
2804
|
/**
|
|
2673
2805
|
* Drain a stream (consume all data without processing)
|
|
@@ -2755,14 +2887,19 @@ export function addAbortSignal(signal, stream) {
|
|
|
2755
2887
|
stream.destroy(new Error("Aborted"));
|
|
2756
2888
|
return stream;
|
|
2757
2889
|
}
|
|
2890
|
+
const cleanup = () => {
|
|
2891
|
+
signal.removeEventListener("abort", onAbort);
|
|
2892
|
+
removeEmitterListener(stream, "close", onClose);
|
|
2893
|
+
};
|
|
2758
2894
|
const onAbort = () => {
|
|
2895
|
+
cleanup();
|
|
2759
2896
|
stream.destroy(new Error("Aborted"));
|
|
2760
2897
|
};
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
});
|
|
2898
|
+
const onClose = () => {
|
|
2899
|
+
cleanup();
|
|
2900
|
+
};
|
|
2901
|
+
signal.addEventListener("abort", onAbort);
|
|
2902
|
+
addEmitterListener(stream, "close", onClose, { once: true });
|
|
2766
2903
|
return stream;
|
|
2767
2904
|
}
|
|
2768
2905
|
/**
|
|
@@ -2771,60 +2908,68 @@ export function addAbortSignal(signal, stream) {
|
|
|
2771
2908
|
export function createDuplex(options) {
|
|
2772
2909
|
const readableObjectMode = options?.readableObjectMode ?? options?.objectMode;
|
|
2773
2910
|
const writableObjectMode = options?.writableObjectMode ?? options?.objectMode;
|
|
2911
|
+
const underlyingWritable = options?.writable;
|
|
2774
2912
|
const duplex = new Duplex({
|
|
2775
2913
|
allowHalfOpen: options?.allowHalfOpen,
|
|
2776
2914
|
readableHighWaterMark: options?.readableHighWaterMark,
|
|
2777
2915
|
writableHighWaterMark: options?.writableHighWaterMark,
|
|
2778
2916
|
readableObjectMode,
|
|
2779
|
-
writableObjectMode
|
|
2917
|
+
writableObjectMode,
|
|
2918
|
+
read: options?.read,
|
|
2919
|
+
write: options?.write ??
|
|
2920
|
+
(underlyingWritable
|
|
2921
|
+
? (chunk, encoding, callback) => {
|
|
2922
|
+
if (typeof underlyingWritable.write === "function") {
|
|
2923
|
+
underlyingWritable.write(chunk, encoding, callback);
|
|
2924
|
+
return;
|
|
2925
|
+
}
|
|
2926
|
+
// Best-effort sync sink
|
|
2927
|
+
try {
|
|
2928
|
+
underlyingWritable.write?.(chunk);
|
|
2929
|
+
callback(null);
|
|
2930
|
+
}
|
|
2931
|
+
catch (err) {
|
|
2932
|
+
callback(err);
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
: undefined),
|
|
2936
|
+
final: options?.final ??
|
|
2937
|
+
(underlyingWritable
|
|
2938
|
+
? (callback) => {
|
|
2939
|
+
if (typeof underlyingWritable.end === "function") {
|
|
2940
|
+
underlyingWritable.end((err) => callback(err ?? null));
|
|
2941
|
+
}
|
|
2942
|
+
else {
|
|
2943
|
+
underlyingWritable.end?.();
|
|
2944
|
+
callback(null);
|
|
2945
|
+
}
|
|
2946
|
+
}
|
|
2947
|
+
: undefined)
|
|
2780
2948
|
});
|
|
2781
|
-
// If
|
|
2949
|
+
// If an underlying readable is provided, forward it into the duplex readable side.
|
|
2782
2950
|
if (options?.readable) {
|
|
2783
2951
|
const readable = options.readable;
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
duplex.end = function (chunkOrCallback, encodingOrCallback, callback) {
|
|
2806
|
-
const cb = typeof chunkOrCallback === "function"
|
|
2807
|
-
? chunkOrCallback
|
|
2808
|
-
: typeof encodingOrCallback === "function"
|
|
2809
|
-
? encodingOrCallback
|
|
2810
|
-
: (callback ?? (() => { }));
|
|
2811
|
-
if (chunkOrCallback !== undefined && typeof chunkOrCallback !== "function") {
|
|
2812
|
-
duplex.write(chunkOrCallback);
|
|
2813
|
-
}
|
|
2814
|
-
// Call custom final handler
|
|
2815
|
-
options.final.call(duplex, (err) => {
|
|
2816
|
-
if (err) {
|
|
2817
|
-
duplex.emit("error", err);
|
|
2818
|
-
}
|
|
2819
|
-
else {
|
|
2820
|
-
duplex.emit("finish");
|
|
2821
|
-
}
|
|
2822
|
-
// Call original end to properly close writable side
|
|
2823
|
-
originalEnd();
|
|
2824
|
-
cb();
|
|
2825
|
-
});
|
|
2826
|
-
return duplex;
|
|
2827
|
-
};
|
|
2952
|
+
const sink = new Writable({
|
|
2953
|
+
objectMode: duplex.readableObjectMode,
|
|
2954
|
+
write(chunk, _encoding, callback) {
|
|
2955
|
+
duplex.push(chunk);
|
|
2956
|
+
callback(null);
|
|
2957
|
+
},
|
|
2958
|
+
final(callback) {
|
|
2959
|
+
duplex.push(null);
|
|
2960
|
+
callback(null);
|
|
2961
|
+
}
|
|
2962
|
+
});
|
|
2963
|
+
if (typeof readable?.on === "function") {
|
|
2964
|
+
const onError = (err) => {
|
|
2965
|
+
duplex.destroy(err);
|
|
2966
|
+
};
|
|
2967
|
+
const cleanupError = addEmitterListener(readable, "error", onError);
|
|
2968
|
+
addEmitterListener(readable, "end", cleanupError, { once: true });
|
|
2969
|
+
addEmitterListener(readable, "close", cleanupError, { once: true });
|
|
2970
|
+
addEmitterListener(sink, "finish", cleanupError, { once: true });
|
|
2971
|
+
}
|
|
2972
|
+
readable.pipe?.(sink);
|
|
2828
2973
|
}
|
|
2829
2974
|
if (options?.destroy) {
|
|
2830
2975
|
const originalDestroy = duplex.destroy.bind(duplex);
|
|
@@ -2848,20 +2993,7 @@ export function createDuplex(options) {
|
|
|
2848
2993
|
*/
|
|
2849
2994
|
export function createReadableFromGenerator(generator, options) {
|
|
2850
2995
|
const readable = new Readable({ ...options, objectMode: options?.objectMode ?? true });
|
|
2851
|
-
(
|
|
2852
|
-
try {
|
|
2853
|
-
for await (const chunk of generator()) {
|
|
2854
|
-
if (!readable.push(chunk)) {
|
|
2855
|
-
// Backpressure
|
|
2856
|
-
await new Promise(resolve => setTimeout(resolve, 0));
|
|
2857
|
-
}
|
|
2858
|
-
}
|
|
2859
|
-
readable.push(null);
|
|
2860
|
-
}
|
|
2861
|
-
catch (err) {
|
|
2862
|
-
readable.destroy(err);
|
|
2863
|
-
}
|
|
2864
|
-
})();
|
|
2996
|
+
pumpAsyncIterableToReadable(readable, generator());
|
|
2865
2997
|
return readable;
|
|
2866
2998
|
}
|
|
2867
2999
|
/**
|
|
@@ -2891,8 +3023,8 @@ export function compose(...transforms) {
|
|
|
2891
3023
|
transform: chunk => chunk
|
|
2892
3024
|
});
|
|
2893
3025
|
}
|
|
2894
|
-
|
|
2895
|
-
if (len === 1
|
|
3026
|
+
// Preserve identity: compose(single) returns the same transform.
|
|
3027
|
+
if (len === 1) {
|
|
2896
3028
|
return transforms[0];
|
|
2897
3029
|
}
|
|
2898
3030
|
// Chain the transforms: first → second → ... → last
|
|
@@ -2903,19 +3035,34 @@ export function compose(...transforms) {
|
|
|
2903
3035
|
transforms[i].pipe(transforms[i + 1]);
|
|
2904
3036
|
}
|
|
2905
3037
|
class ComposedTransform extends Transform {
|
|
2906
|
-
constructor() {
|
|
2907
|
-
super(
|
|
3038
|
+
constructor(options) {
|
|
3039
|
+
super(options);
|
|
2908
3040
|
this._dataForwarding = false;
|
|
2909
3041
|
this._endForwarding = false;
|
|
3042
|
+
this._dataForwardCleanup = null;
|
|
3043
|
+
this._endForwardCleanup = null;
|
|
3044
|
+
this._errorForwardCleanup = [];
|
|
3045
|
+
for (const t of transforms) {
|
|
3046
|
+
const onError = (err) => {
|
|
3047
|
+
this.emit("error", err);
|
|
3048
|
+
};
|
|
3049
|
+
this._errorForwardCleanup.push(addEmitterListener(t, "error", onError));
|
|
3050
|
+
}
|
|
2910
3051
|
}
|
|
2911
3052
|
on(event, listener) {
|
|
2912
3053
|
if (event === "data" && !this._dataForwarding) {
|
|
2913
3054
|
this._dataForwarding = true;
|
|
2914
|
-
|
|
3055
|
+
const onData = (chunk) => {
|
|
3056
|
+
this.emit("data", chunk);
|
|
3057
|
+
};
|
|
3058
|
+
this._dataForwardCleanup = addEmitterListener(last, "data", onData);
|
|
2915
3059
|
}
|
|
2916
3060
|
if (event === "end" && !this._endForwarding) {
|
|
2917
3061
|
this._endForwarding = true;
|
|
2918
|
-
|
|
3062
|
+
const onEnd = () => {
|
|
3063
|
+
this.emit("end");
|
|
3064
|
+
};
|
|
3065
|
+
this._endForwardCleanup = addEmitterListener(last, "end", onEnd, { once: true });
|
|
2919
3066
|
}
|
|
2920
3067
|
return super.on(event, listener);
|
|
2921
3068
|
}
|
|
@@ -2941,6 +3088,18 @@ export function compose(...transforms) {
|
|
|
2941
3088
|
return last.pipe(destination);
|
|
2942
3089
|
}
|
|
2943
3090
|
destroy(error) {
|
|
3091
|
+
if (this._dataForwardCleanup) {
|
|
3092
|
+
this._dataForwardCleanup();
|
|
3093
|
+
this._dataForwardCleanup = null;
|
|
3094
|
+
}
|
|
3095
|
+
if (this._endForwardCleanup) {
|
|
3096
|
+
this._endForwardCleanup();
|
|
3097
|
+
this._endForwardCleanup = null;
|
|
3098
|
+
}
|
|
3099
|
+
for (let i = this._errorForwardCleanup.length - 1; i >= 0; i--) {
|
|
3100
|
+
this._errorForwardCleanup[i]();
|
|
3101
|
+
}
|
|
3102
|
+
this._errorForwardCleanup.length = 0;
|
|
2944
3103
|
for (const t of transforms) {
|
|
2945
3104
|
t.destroy(error);
|
|
2946
3105
|
}
|
|
@@ -2964,12 +3123,6 @@ export function compose(...transforms) {
|
|
|
2964
3123
|
objectMode: first?.objectMode ?? true,
|
|
2965
3124
|
transform: chunk => chunk
|
|
2966
3125
|
});
|
|
2967
|
-
// Forward errors from any transform
|
|
2968
|
-
for (const t of transforms) {
|
|
2969
|
-
t.on("error", (err) => {
|
|
2970
|
-
composed.emit("error", err);
|
|
2971
|
-
});
|
|
2972
|
-
}
|
|
2973
3126
|
// Reflect underlying readability/writability like the previous duck-typed wrapper
|
|
2974
3127
|
Object.defineProperty(composed, "readable", {
|
|
2975
3128
|
get: () => last.readable
|