@cj-tech-master/excelts 4.2.0-canary.20260110111632.c88c61c → 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/{esm/modules/archive → browser/modules/archive/compression}/compress.base.js +1 -1
- package/dist/{types/modules/archive → browser/modules/archive/compression}/compress.browser.d.ts +2 -8
- package/dist/browser/modules/archive/{compress.browser.js → compression/compress.browser.js} +3 -11
- package/dist/browser/modules/archive/{compress.d.ts → compression/compress.d.ts} +2 -2
- package/dist/{esm/modules/archive → browser/modules/archive/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} +36 -2
- 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} +371 -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} +6 -5
- package/dist/browser/modules/archive/{zip-parser.d.ts → 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/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/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 -30
- package/dist/browser/modules/stream/streams.browser.js +830 -710
- package/dist/browser/modules/stream/streams.js +140 -58
- package/dist/cjs/modules/archive/{compress.base.js → compression/compress.base.js} +1 -1
- package/dist/cjs/modules/archive/{compress.browser.js → compression/compress.browser.js} +3 -11
- 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} +372 -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/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/xlsx.browser.js +6 -9
- package/dist/cjs/modules/excel/xlsx/xlsx.js +2 -2
- package/dist/cjs/modules/stream/streams.browser.js +830 -710
- package/dist/cjs/modules/stream/streams.js +140 -58
- package/dist/esm/index.browser.js +12 -0
- package/dist/{browser/modules/archive → esm/modules/archive/compression}/compress.base.js +1 -1
- package/dist/esm/modules/archive/{compress.browser.js → compression/compress.browser.js} +3 -11
- package/dist/{browser/modules/archive → esm/modules/archive/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} +371 -110
- package/dist/{browser/modules/archive/parse.js → esm/modules/archive/unzip/stream.js} +6 -5
- 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/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/xlsx.browser.js +3 -6
- package/dist/esm/modules/excel/xlsx/xlsx.js +1 -1
- package/dist/esm/modules/stream/streams.browser.js +830 -710
- package/dist/esm/modules/stream/streams.js +140 -58
- package/dist/iife/THIRD_PARTY_NOTICES.md +0 -31
- package/dist/iife/excelts.iife.js +6190 -4400
- 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.browser.d.ts +2 -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} +38 -4
- 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/types/modules/archive/{zip-parser.d.ts → 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/stream/workbook-writer.browser.d.ts +1 -1
- package/dist/types/modules/stream/streams.browser.d.ts +28 -30
- package/package.json +5 -1
- 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 -644
- 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 -666
- 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 -644
- 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/{compress.base.d.ts → compression/compress.base.d.ts} +0 -0
- /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/types/modules/archive/{compress.base.d.ts → compression/compress.base.d.ts} +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
|
|
@@ -399,26 +448,36 @@ export class Readable extends EventEmitter {
|
|
|
399
448
|
}
|
|
400
449
|
this._pipeTo.push(dest);
|
|
401
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
|
+
};
|
|
402
464
|
const dataListener = (chunk) => {
|
|
403
465
|
// Call destination's write() method (not internal _writable.write())
|
|
404
466
|
// This ensures Transform.write() logic runs properly
|
|
405
467
|
const canWrite = dest.write(chunk);
|
|
406
468
|
if (!canWrite) {
|
|
407
469
|
this.pause();
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
const resumeOnce = () => {
|
|
413
|
-
if (typeof eventTarget.off === "function") {
|
|
414
|
-
eventTarget.off("drain", resumeOnce);
|
|
415
|
-
}
|
|
416
|
-
else if (typeof eventTarget.removeListener === "function") {
|
|
417
|
-
eventTarget.removeListener("drain", resumeOnce);
|
|
418
|
-
}
|
|
470
|
+
// Install a removable, once-style drain listener.
|
|
471
|
+
if (!drainListener) {
|
|
472
|
+
drainListener = () => {
|
|
473
|
+
removeDrainListener();
|
|
419
474
|
this.resume();
|
|
420
475
|
};
|
|
421
|
-
eventTarget.on("drain",
|
|
476
|
+
eventTarget.on("drain", drainListener);
|
|
477
|
+
const entry = this._pipeListeners.get(dest);
|
|
478
|
+
if (entry) {
|
|
479
|
+
entry.drain = drainListener;
|
|
480
|
+
}
|
|
422
481
|
}
|
|
423
482
|
}
|
|
424
483
|
};
|
|
@@ -438,7 +497,8 @@ export class Readable extends EventEmitter {
|
|
|
438
497
|
this._pipeListeners.set(dest, {
|
|
439
498
|
data: dataListener,
|
|
440
499
|
end: endListener,
|
|
441
|
-
error: errorListener
|
|
500
|
+
error: errorListener,
|
|
501
|
+
eventTarget
|
|
442
502
|
});
|
|
443
503
|
this.on("data", dataListener);
|
|
444
504
|
this.once("end", endListener);
|
|
@@ -461,6 +521,14 @@ export class Readable extends EventEmitter {
|
|
|
461
521
|
this.off("data", listeners.data);
|
|
462
522
|
this.off("end", listeners.end);
|
|
463
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
|
+
}
|
|
464
532
|
this._pipeListeners.delete(destination);
|
|
465
533
|
}
|
|
466
534
|
}
|
|
@@ -472,6 +540,14 @@ export class Readable extends EventEmitter {
|
|
|
472
540
|
this.off("data", listeners.data);
|
|
473
541
|
this.off("end", listeners.end);
|
|
474
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
|
+
}
|
|
475
551
|
this._pipeListeners.delete(target);
|
|
476
552
|
}
|
|
477
553
|
}
|
|
@@ -490,12 +566,26 @@ export class Readable extends EventEmitter {
|
|
|
490
566
|
}
|
|
491
567
|
this._destroyed = true;
|
|
492
568
|
this._ended = true;
|
|
569
|
+
// Ensure we detach from destinations to avoid leaking listeners.
|
|
570
|
+
this.unpipe();
|
|
493
571
|
if (error) {
|
|
494
572
|
this._errored = error;
|
|
495
573
|
this.emit("error", error);
|
|
496
574
|
}
|
|
497
575
|
if (this._reader) {
|
|
498
|
-
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
|
+
});
|
|
499
589
|
}
|
|
500
590
|
this._closed = true;
|
|
501
591
|
this.emit("close");
|
|
@@ -574,18 +664,38 @@ export class Readable extends EventEmitter {
|
|
|
574
664
|
const { done, value } = await this._reader.read();
|
|
575
665
|
// Check _pushMode again after async read - if push() was called, stop reading
|
|
576
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
|
+
}
|
|
577
677
|
break;
|
|
578
678
|
}
|
|
579
679
|
if (done) {
|
|
580
680
|
this._ended = true;
|
|
581
|
-
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
|
+
}
|
|
582
692
|
break;
|
|
583
693
|
}
|
|
584
694
|
if (value !== undefined) {
|
|
585
695
|
// In flowing mode, emit data directly without buffering
|
|
586
696
|
// Only buffer if not flowing (paused mode)
|
|
587
697
|
if (this._flowing) {
|
|
588
|
-
this.emit("data", value);
|
|
698
|
+
this.emit("data", this._applyEncoding(value));
|
|
589
699
|
}
|
|
590
700
|
else {
|
|
591
701
|
this._buffer.push(value);
|
|
@@ -598,6 +708,16 @@ export class Readable extends EventEmitter {
|
|
|
598
708
|
}
|
|
599
709
|
catch (err) {
|
|
600
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
|
+
}
|
|
601
721
|
}
|
|
602
722
|
finally {
|
|
603
723
|
this._reading = false;
|
|
@@ -605,7 +725,8 @@ export class Readable extends EventEmitter {
|
|
|
605
725
|
}
|
|
606
726
|
/**
|
|
607
727
|
* Async iterator support
|
|
608
|
-
* Uses
|
|
728
|
+
* Uses a unified event-queue iterator with simple backpressure.
|
|
729
|
+
* This matches Node's behavior more closely (iterator drives flowing mode).
|
|
609
730
|
*/
|
|
610
731
|
async *[Symbol.asyncIterator]() {
|
|
611
732
|
// First yield any buffered data
|
|
@@ -614,117 +735,124 @@ export class Readable extends EventEmitter {
|
|
|
614
735
|
if (!this.objectMode) {
|
|
615
736
|
this._bufferSize -= this._getChunkSize(chunk);
|
|
616
737
|
}
|
|
617
|
-
yield chunk;
|
|
738
|
+
yield this._applyEncoding(chunk);
|
|
618
739
|
}
|
|
619
|
-
// If already ended, we're done
|
|
620
740
|
if (this._ended) {
|
|
621
741
|
return;
|
|
622
742
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
let resolveNext = null;
|
|
629
|
-
let rejectNext = null;
|
|
630
|
-
let done = false;
|
|
631
|
-
let streamError = null;
|
|
632
|
-
let dataQueueIndex = 0;
|
|
633
|
-
const dataHandler = (chunk) => {
|
|
634
|
-
if (resolveNext) {
|
|
635
|
-
resolveNext(chunk);
|
|
636
|
-
resolveNext = null;
|
|
637
|
-
rejectNext = null;
|
|
638
|
-
}
|
|
639
|
-
else {
|
|
640
|
-
dataQueue.push(chunk);
|
|
641
|
-
}
|
|
642
|
-
};
|
|
643
|
-
const endHandler = () => {
|
|
644
|
-
done = true;
|
|
645
|
-
if (resolveNext) {
|
|
646
|
-
resolveNext(null);
|
|
647
|
-
resolveNext = null;
|
|
648
|
-
rejectNext = null;
|
|
649
|
-
}
|
|
650
|
-
};
|
|
651
|
-
const errorHandler = (err) => {
|
|
652
|
-
done = true;
|
|
653
|
-
streamError = err;
|
|
654
|
-
if (rejectNext) {
|
|
655
|
-
rejectNext(err);
|
|
656
|
-
resolveNext = null;
|
|
657
|
-
rejectNext = null;
|
|
658
|
-
}
|
|
659
|
-
};
|
|
660
|
-
const closeHandler = () => {
|
|
661
|
-
// If stream closed without end event (e.g., after destroy()),
|
|
662
|
-
// treat it as done
|
|
663
|
-
done = true;
|
|
664
|
-
if (resolveNext) {
|
|
665
|
-
resolveNext(null);
|
|
666
|
-
resolveNext = null;
|
|
667
|
-
rejectNext = null;
|
|
668
|
-
}
|
|
669
|
-
};
|
|
670
|
-
this.on("data", dataHandler);
|
|
671
|
-
this.on("end", endHandler);
|
|
672
|
-
this.on("error", errorHandler);
|
|
673
|
-
this.on("close", closeHandler);
|
|
674
|
-
try {
|
|
675
|
-
// Enter flowing mode
|
|
676
|
-
this.resume();
|
|
677
|
-
while (!done || dataQueueIndex < dataQueue.length) {
|
|
678
|
-
// Check for error before processing
|
|
679
|
-
if (streamError) {
|
|
680
|
-
throw streamError;
|
|
681
|
-
}
|
|
682
|
-
if (dataQueueIndex < dataQueue.length) {
|
|
683
|
-
const chunk = dataQueue[dataQueueIndex++];
|
|
684
|
-
if (dataQueueIndex >= 1024 && dataQueueIndex * 2 >= dataQueue.length) {
|
|
685
|
-
dataQueue.splice(0, dataQueueIndex);
|
|
686
|
-
dataQueueIndex = 0;
|
|
687
|
-
}
|
|
688
|
-
yield chunk;
|
|
689
|
-
}
|
|
690
|
-
else if (!done) {
|
|
691
|
-
const chunk = await new Promise((resolve, reject) => {
|
|
692
|
-
resolveNext = resolve;
|
|
693
|
-
rejectNext = reject;
|
|
694
|
-
});
|
|
695
|
-
if (chunk !== null) {
|
|
696
|
-
yield chunk;
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
// Check for error after loop
|
|
701
|
-
if (streamError) {
|
|
702
|
-
throw streamError;
|
|
703
|
-
}
|
|
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;
|
|
704
748
|
}
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
this.off("end", endHandler);
|
|
708
|
-
this.off("error", errorHandler);
|
|
709
|
-
this.off("close", closeHandler);
|
|
749
|
+
if (chunk instanceof Uint8Array) {
|
|
750
|
+
return chunk.byteLength;
|
|
710
751
|
}
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
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);
|
|
717
810
|
try {
|
|
811
|
+
// Iterator consumption should drive the stream.
|
|
812
|
+
this.resume();
|
|
718
813
|
while (true) {
|
|
719
|
-
|
|
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
|
+
}
|
|
720
831
|
if (done) {
|
|
721
832
|
break;
|
|
722
833
|
}
|
|
723
|
-
|
|
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;
|
|
724
849
|
}
|
|
725
850
|
}
|
|
726
851
|
finally {
|
|
727
|
-
this.
|
|
852
|
+
this.off("data", dataHandler);
|
|
853
|
+
this.off("end", endHandler);
|
|
854
|
+
this.off("error", errorHandler);
|
|
855
|
+
this.off("close", closeHandler);
|
|
728
856
|
}
|
|
729
857
|
}
|
|
730
858
|
/**
|
|
@@ -943,6 +1071,32 @@ export class Readable extends EventEmitter {
|
|
|
943
1071
|
return stream;
|
|
944
1072
|
}
|
|
945
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
|
+
}
|
|
946
1100
|
// =============================================================================
|
|
947
1101
|
// Writable Stream Wrapper
|
|
948
1102
|
// =============================================================================
|
|
@@ -960,10 +1114,12 @@ export class Writable extends EventEmitter {
|
|
|
960
1114
|
this._closed = false;
|
|
961
1115
|
this._pendingWrites = 0;
|
|
962
1116
|
this._writableLength = 0;
|
|
1117
|
+
this._needDrain = false;
|
|
963
1118
|
this._corked = 0;
|
|
964
1119
|
this._corkedChunks = [];
|
|
965
1120
|
this._defaultEncoding = "utf8";
|
|
966
1121
|
this._aborted = false;
|
|
1122
|
+
this._ownsStream = false;
|
|
967
1123
|
this.objectMode = options?.objectMode ?? false;
|
|
968
1124
|
this.writableHighWaterMark = options?.highWaterMark ?? 16384;
|
|
969
1125
|
this.autoDestroy = options?.autoDestroy ?? true;
|
|
@@ -979,8 +1135,10 @@ export class Writable extends EventEmitter {
|
|
|
979
1135
|
}
|
|
980
1136
|
if (options?.stream) {
|
|
981
1137
|
this._stream = options.stream;
|
|
1138
|
+
this._ownsStream = false;
|
|
982
1139
|
}
|
|
983
1140
|
else {
|
|
1141
|
+
this._ownsStream = true;
|
|
984
1142
|
// Create bound references to instance properties/methods for use in WritableStream callbacks
|
|
985
1143
|
const getWriteFunc = () => this._writeFunc;
|
|
986
1144
|
const getFinalFunc = () => this._finalFunc;
|
|
@@ -1087,25 +1245,37 @@ export class Writable extends EventEmitter {
|
|
|
1087
1245
|
this._writableLength += chunkSize;
|
|
1088
1246
|
return this._writableLength < this.writableHighWaterMark;
|
|
1089
1247
|
}
|
|
1090
|
-
|
|
1248
|
+
const ok = this._doWrite(chunk, cb);
|
|
1249
|
+
if (!ok) {
|
|
1250
|
+
this._needDrain = true;
|
|
1251
|
+
}
|
|
1252
|
+
return ok;
|
|
1091
1253
|
}
|
|
1092
1254
|
_doWrite(chunk, callback) {
|
|
1093
1255
|
// Track pending writes for writableLength
|
|
1094
1256
|
const chunkSize = this._getChunkSize(chunk);
|
|
1095
1257
|
this._pendingWrites++;
|
|
1096
1258
|
this._writableLength += chunkSize;
|
|
1097
|
-
this._getWriter()
|
|
1259
|
+
const writer = this._getWriter();
|
|
1260
|
+
writer
|
|
1098
1261
|
.write(chunk)
|
|
1099
1262
|
.then(() => {
|
|
1100
1263
|
this._pendingWrites--;
|
|
1101
1264
|
this._writableLength -= chunkSize;
|
|
1102
|
-
this.
|
|
1265
|
+
if (this._needDrain && this._writableLength < this.writableHighWaterMark) {
|
|
1266
|
+
this._needDrain = false;
|
|
1267
|
+
this.emit("drain");
|
|
1268
|
+
}
|
|
1103
1269
|
callback?.(null);
|
|
1104
1270
|
})
|
|
1105
1271
|
.catch(err => {
|
|
1106
1272
|
this._pendingWrites--;
|
|
1107
1273
|
this._writableLength -= chunkSize;
|
|
1108
|
-
|
|
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
|
+
}
|
|
1109
1279
|
callback?.(err);
|
|
1110
1280
|
});
|
|
1111
1281
|
// Return false if we've exceeded high water mark (for backpressure)
|
|
@@ -1136,12 +1306,29 @@ export class Writable extends EventEmitter {
|
|
|
1136
1306
|
: callback;
|
|
1137
1307
|
const finish = async () => {
|
|
1138
1308
|
try {
|
|
1309
|
+
const writer = this._getWriter();
|
|
1139
1310
|
if (chunk !== undefined) {
|
|
1140
|
-
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
|
+
}
|
|
1141
1331
|
}
|
|
1142
|
-
await this._getWriter().close();
|
|
1143
|
-
this._finished = true;
|
|
1144
|
-
this.emit("finish");
|
|
1145
1332
|
if (cb) {
|
|
1146
1333
|
cb();
|
|
1147
1334
|
}
|
|
@@ -1162,12 +1349,24 @@ export class Writable extends EventEmitter {
|
|
|
1162
1349
|
}
|
|
1163
1350
|
this._destroyed = true;
|
|
1164
1351
|
this._ended = true;
|
|
1165
|
-
if (error) {
|
|
1352
|
+
if (error && !this._errored) {
|
|
1166
1353
|
this._errored = error;
|
|
1167
1354
|
this.emit("error", error);
|
|
1168
1355
|
}
|
|
1169
1356
|
if (this._writer) {
|
|
1170
|
-
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
|
+
});
|
|
1171
1370
|
}
|
|
1172
1371
|
this._closed = true;
|
|
1173
1372
|
this.emit("close");
|
|
@@ -1318,342 +1517,259 @@ export function normalizeWritable(stream) {
|
|
|
1318
1517
|
*/
|
|
1319
1518
|
export class Transform extends EventEmitter {
|
|
1320
1519
|
/**
|
|
1321
|
-
* Push data to the readable side (Node.js compatibility)
|
|
1322
|
-
*
|
|
1520
|
+
* Push data to the readable side (Node.js compatibility).
|
|
1521
|
+
* Intended to be called from within transform/flush.
|
|
1323
1522
|
*/
|
|
1324
1523
|
push(chunk) {
|
|
1325
|
-
|
|
1326
|
-
return false;
|
|
1327
|
-
}
|
|
1328
|
-
if (this._transformController) {
|
|
1329
|
-
// If we're in a transform callback, enqueue directly
|
|
1330
|
-
this._transformController.enqueue(chunk);
|
|
1331
|
-
}
|
|
1332
|
-
else {
|
|
1333
|
-
// Otherwise buffer for later
|
|
1334
|
-
this._pushBuffer.push(chunk);
|
|
1335
|
-
}
|
|
1336
|
-
return true;
|
|
1524
|
+
return this._readable.push(chunk);
|
|
1337
1525
|
}
|
|
1338
1526
|
constructor(options) {
|
|
1339
1527
|
super();
|
|
1340
|
-
this._ended = false;
|
|
1341
1528
|
this._destroyed = false;
|
|
1529
|
+
this._ended = false;
|
|
1342
1530
|
this._errored = false;
|
|
1343
|
-
// Buffer for Node.js style push() calls during transform
|
|
1344
|
-
this._pushBuffer = [];
|
|
1345
|
-
// Controller for enqueueing pushed data (set during transform execution)
|
|
1346
|
-
this._transformController = null;
|
|
1347
|
-
// Buffer for writes that occur after end() but before writable is closed
|
|
1348
|
-
this._pendingEndWrites = [];
|
|
1349
|
-
// Whether end() has been called but writable not yet closed
|
|
1350
|
-
this._endPending = false;
|
|
1351
|
-
// Track if we've already set up data forwarding
|
|
1352
1531
|
this._dataForwardingSetup = false;
|
|
1353
|
-
|
|
1354
|
-
this.
|
|
1355
|
-
|
|
1356
|
-
this._readableConsuming = false;
|
|
1357
|
-
/** @internal - buffer for auto-consumed data */
|
|
1358
|
-
this._autoConsumedBuffer = [];
|
|
1359
|
-
this._autoConsumedBufferIndex = 0;
|
|
1360
|
-
/** @internal - whether auto-consume has ended */
|
|
1361
|
-
this._autoConsumeEnded = false;
|
|
1362
|
-
/** @internal - promise that resolves when auto-consume finishes */
|
|
1363
|
-
this._autoConsumePromise = null;
|
|
1364
|
-
/** @internal - list of piped destinations for forwarding auto-consumed data */
|
|
1365
|
-
this._pipeDestinations = [];
|
|
1532
|
+
this._endTimer = null;
|
|
1533
|
+
this._webStream = null;
|
|
1534
|
+
this._sideForwardingCleanup = null;
|
|
1366
1535
|
this.objectMode = options?.objectMode ?? false;
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
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));
|
|
1382
1555
|
}
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
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;
|
|
1392
1587
|
}
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
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;
|
|
1400
1638
|
}
|
|
1401
|
-
const
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
// Check for subclass _transform override first
|
|
1414
|
-
if (hasSubclassTransform()) {
|
|
1415
|
-
// Call subclass _transform method (Node.js style)
|
|
1416
|
-
// _transform signature is (chunk, encoding, callback)
|
|
1417
|
-
await new Promise((resolve, reject) => {
|
|
1418
|
-
this._transform(chunk, "utf8", (err, data) => {
|
|
1419
|
-
if (err) {
|
|
1420
|
-
reject(err);
|
|
1421
|
-
}
|
|
1422
|
-
else {
|
|
1423
|
-
if (data !== undefined) {
|
|
1424
|
-
controller.enqueue(data);
|
|
1425
|
-
}
|
|
1426
|
-
resolve();
|
|
1427
|
-
}
|
|
1428
|
-
});
|
|
1429
|
-
});
|
|
1430
|
-
}
|
|
1431
|
-
else if (userTransform) {
|
|
1432
|
-
const transformParamCount = userTransform.length;
|
|
1433
|
-
if (transformParamCount >= 3) {
|
|
1434
|
-
// Node.js style: transform(chunk, encoding, callback)
|
|
1435
|
-
await new Promise((resolve, reject) => {
|
|
1436
|
-
userTransform.call(getInstance(), chunk, "utf8", (err, data) => {
|
|
1437
|
-
if (err) {
|
|
1438
|
-
reject(err);
|
|
1439
|
-
}
|
|
1440
|
-
else {
|
|
1441
|
-
if (data !== undefined) {
|
|
1442
|
-
controller.enqueue(data);
|
|
1443
|
-
}
|
|
1444
|
-
resolve();
|
|
1445
|
-
}
|
|
1446
|
-
});
|
|
1447
|
-
});
|
|
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;
|
|
1448
1651
|
}
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
userTransform.call(getInstance(), chunk, (err, data) => {
|
|
1452
|
-
if (err) {
|
|
1453
|
-
reject(err);
|
|
1454
|
-
}
|
|
1455
|
-
else {
|
|
1456
|
-
if (data !== undefined) {
|
|
1457
|
-
controller.enqueue(data);
|
|
1458
|
-
}
|
|
1459
|
-
resolve();
|
|
1460
|
-
}
|
|
1461
|
-
});
|
|
1462
|
-
});
|
|
1652
|
+
if (data !== undefined) {
|
|
1653
|
+
this.push(data);
|
|
1463
1654
|
}
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
controller.enqueue(result);
|
|
1476
|
-
}
|
|
1477
|
-
}
|
|
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;
|
|
1478
1666
|
}
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
}
|
|
1484
|
-
}
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
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);
|
|
1491
1680
|
}
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
if (flushParamCount >= 1) {
|
|
1515
|
-
// Node.js style: flush(callback)
|
|
1516
|
-
await new Promise((resolve, reject) => {
|
|
1517
|
-
userFlush.call(getInstance(), (err, data) => {
|
|
1518
|
-
if (err) {
|
|
1519
|
-
reject(err);
|
|
1520
|
-
}
|
|
1521
|
-
else {
|
|
1522
|
-
if (data !== undefined) {
|
|
1523
|
-
controller.enqueue(data);
|
|
1524
|
-
}
|
|
1525
|
-
resolve();
|
|
1526
|
-
}
|
|
1527
|
-
});
|
|
1528
|
-
});
|
|
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;
|
|
1529
1703
|
}
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
const result = userFlush.call(getInstance());
|
|
1533
|
-
if (result && typeof result.then === "function") {
|
|
1534
|
-
const awaitedResult = await result;
|
|
1535
|
-
if (awaitedResult !== undefined && awaitedResult !== null) {
|
|
1536
|
-
controller.enqueue(awaitedResult);
|
|
1537
|
-
}
|
|
1538
|
-
}
|
|
1539
|
-
else {
|
|
1540
|
-
if (result !== undefined && result !== null) {
|
|
1541
|
-
controller.enqueue(result);
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1704
|
+
if (data !== undefined) {
|
|
1705
|
+
this.push(data);
|
|
1544
1706
|
}
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
}
|
|
1548
|
-
|
|
1549
|
-
controller.error(err);
|
|
1550
|
-
emitEvent("error", err);
|
|
1551
|
-
}
|
|
1552
|
-
finally {
|
|
1553
|
-
setController(null);
|
|
1554
|
-
}
|
|
1707
|
+
resolve();
|
|
1708
|
+
});
|
|
1709
|
+
});
|
|
1710
|
+
return;
|
|
1555
1711
|
}
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
objectMode: this.objectMode
|
|
1560
|
-
});
|
|
1561
|
-
this._writable = new Writable({
|
|
1562
|
-
stream: this._stream.writable,
|
|
1563
|
-
objectMode: this.objectMode
|
|
1564
|
-
});
|
|
1565
|
-
// Forward non-data events (data forwarding is lazy to avoid premature flowing)
|
|
1566
|
-
this._readable.on("end", () => this.emit("end"));
|
|
1567
|
-
// Only forward errors if not already errored (to prevent duplicate events)
|
|
1568
|
-
this._readable.on("error", err => {
|
|
1569
|
-
if (!this._errored) {
|
|
1570
|
-
this._errored = true;
|
|
1571
|
-
this.emit("error", err);
|
|
1712
|
+
const userFlush = this._flushImpl;
|
|
1713
|
+
if (!userFlush) {
|
|
1714
|
+
return;
|
|
1572
1715
|
}
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
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;
|
|
1581
1731
|
}
|
|
1582
|
-
|
|
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
|
+
}
|
|
1583
1748
|
}
|
|
1584
1749
|
/**
|
|
1585
|
-
* Override on to
|
|
1750
|
+
* Override on() to lazily forward readable 'data' events.
|
|
1751
|
+
* Avoids starting flowing mode unless requested.
|
|
1586
1752
|
*/
|
|
1587
1753
|
on(event, listener) {
|
|
1588
|
-
// Set up data forwarding when first external data listener is added
|
|
1589
1754
|
if (event === "data" && !this._dataForwardingSetup) {
|
|
1590
1755
|
this._dataForwardingSetup = true;
|
|
1591
|
-
this._readable.on("data",
|
|
1592
|
-
}
|
|
1593
|
-
super.on(event, listener);
|
|
1594
|
-
// When data listener is added, mark as having consumer
|
|
1595
|
-
// and start the readable in flowing mode
|
|
1596
|
-
if (event === "data") {
|
|
1597
|
-
this._hasDataConsumer = true;
|
|
1598
|
-
this._readable.resume();
|
|
1756
|
+
this._readable.on("data", chunk => this.emit("data", chunk));
|
|
1599
1757
|
}
|
|
1600
|
-
return
|
|
1758
|
+
return super.on(event, listener);
|
|
1601
1759
|
}
|
|
1602
1760
|
write(chunk, encodingOrCallback, callback) {
|
|
1603
1761
|
const cb = typeof encodingOrCallback === "function" ? encodingOrCallback : callback;
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
cb?.(err);
|
|
1608
|
-
return false;
|
|
1609
|
-
}
|
|
1610
|
-
// Ensure readable is being consumed to allow transform to execute
|
|
1611
|
-
// This matches Node.js behavior where transform executes immediately on write
|
|
1612
|
-
// Only auto-consume if no explicit consumer (data listener or pipe)
|
|
1613
|
-
if (!this._readableConsuming && !this._hasDataConsumer) {
|
|
1614
|
-
this._readableConsuming = true;
|
|
1615
|
-
this._startAutoConsume();
|
|
1616
|
-
}
|
|
1617
|
-
// If end() was called but writable not yet closed, buffer the write
|
|
1618
|
-
// This allows writes during data event handlers to be processed
|
|
1619
|
-
if (this._endPending) {
|
|
1620
|
-
this._pendingEndWrites.push({ chunk, callback: cb });
|
|
1621
|
-
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();
|
|
1622
1765
|
}
|
|
1623
1766
|
return this._writable.write(chunk, cb);
|
|
1624
1767
|
}
|
|
1625
|
-
/** @internal - auto-consume readable to allow transform to execute */
|
|
1626
|
-
_startAutoConsume() {
|
|
1627
|
-
this._autoConsumePromise = (async () => {
|
|
1628
|
-
try {
|
|
1629
|
-
for await (const chunk of this._readable) {
|
|
1630
|
-
// Buffer the data for later retrieval
|
|
1631
|
-
this._autoConsumedBuffer.push(chunk);
|
|
1632
|
-
// Forward to any piped destinations
|
|
1633
|
-
for (const dest of this._pipeDestinations) {
|
|
1634
|
-
dest.write(chunk);
|
|
1635
|
-
}
|
|
1636
|
-
// Also emit data event for listeners
|
|
1637
|
-
this.emit("data", chunk);
|
|
1638
|
-
}
|
|
1639
|
-
this._autoConsumeEnded = true;
|
|
1640
|
-
// End all piped destinations
|
|
1641
|
-
for (const dest of this._pipeDestinations) {
|
|
1642
|
-
dest.end();
|
|
1643
|
-
}
|
|
1644
|
-
this.emit("end");
|
|
1645
|
-
}
|
|
1646
|
-
catch (err) {
|
|
1647
|
-
this.emit("error", err);
|
|
1648
|
-
}
|
|
1649
|
-
})();
|
|
1650
|
-
}
|
|
1651
1768
|
end(chunkOrCallback, encodingOrCallback, callback) {
|
|
1652
1769
|
if (this._ended) {
|
|
1653
1770
|
return this;
|
|
1654
1771
|
}
|
|
1655
1772
|
this._ended = true;
|
|
1656
|
-
this._endPending = true;
|
|
1657
1773
|
const chunk = typeof chunkOrCallback === "function" ? undefined : chunkOrCallback;
|
|
1658
1774
|
const cb = typeof chunkOrCallback === "function"
|
|
1659
1775
|
? chunkOrCallback
|
|
@@ -1666,18 +1782,7 @@ export class Transform extends EventEmitter {
|
|
|
1666
1782
|
if (chunk !== undefined) {
|
|
1667
1783
|
this._writable.write(chunk);
|
|
1668
1784
|
}
|
|
1669
|
-
|
|
1670
|
-
// processing and data events complete before we close the writable.
|
|
1671
|
-
// Microtasks run before the TransformStream processes data.
|
|
1672
|
-
setTimeout(() => {
|
|
1673
|
-
// Process any writes that occurred during data events
|
|
1674
|
-
for (const { chunk: pendingChunk, callback } of this._pendingEndWrites) {
|
|
1675
|
-
this._writable.write(pendingChunk, callback);
|
|
1676
|
-
}
|
|
1677
|
-
this._pendingEndWrites = [];
|
|
1678
|
-
this._endPending = false;
|
|
1679
|
-
this._writable.end();
|
|
1680
|
-
}, 0);
|
|
1785
|
+
this._scheduleEnd();
|
|
1681
1786
|
return this;
|
|
1682
1787
|
}
|
|
1683
1788
|
/**
|
|
@@ -1687,31 +1792,9 @@ export class Transform extends EventEmitter {
|
|
|
1687
1792
|
return this._readable.read(size);
|
|
1688
1793
|
}
|
|
1689
1794
|
/**
|
|
1690
|
-
* Pipe
|
|
1795
|
+
* Pipe readable side to destination
|
|
1691
1796
|
*/
|
|
1692
1797
|
pipe(destination) {
|
|
1693
|
-
// Mark as having consumer to prevent new auto-consume from starting
|
|
1694
|
-
this._hasDataConsumer = true;
|
|
1695
|
-
// Get the writable target - handle both Transform (with internal _writable) and plain Writable
|
|
1696
|
-
const dest = destination;
|
|
1697
|
-
const target = dest?._writable ?? dest;
|
|
1698
|
-
// Register destination for forwarding
|
|
1699
|
-
this._pipeDestinations.push(target);
|
|
1700
|
-
// If auto-consume is running or has run, we need to handle buffered data ourselves
|
|
1701
|
-
if (this._readableConsuming) {
|
|
1702
|
-
// Forward any buffered data from auto-consume to the destination
|
|
1703
|
-
for (let i = 0; i < this._autoConsumedBuffer.length; i++) {
|
|
1704
|
-
target.write(this._autoConsumedBuffer[i]);
|
|
1705
|
-
}
|
|
1706
|
-
// If auto-consume has ended, end the destination too
|
|
1707
|
-
if (this._autoConsumeEnded) {
|
|
1708
|
-
target.end();
|
|
1709
|
-
}
|
|
1710
|
-
// Don't call _readable.pipe() - auto-consume already consumed _readable
|
|
1711
|
-
// Future data will be forwarded via the 'data' event listener below
|
|
1712
|
-
return destination;
|
|
1713
|
-
}
|
|
1714
|
-
// No auto-consume running - use normal pipe through _readable
|
|
1715
1798
|
return this._readable.pipe(destination);
|
|
1716
1799
|
}
|
|
1717
1800
|
/**
|
|
@@ -1749,6 +1832,10 @@ export class Transform extends EventEmitter {
|
|
|
1749
1832
|
return;
|
|
1750
1833
|
}
|
|
1751
1834
|
this._destroyed = true;
|
|
1835
|
+
if (this._sideForwardingCleanup) {
|
|
1836
|
+
this._sideForwardingCleanup();
|
|
1837
|
+
this._sideForwardingCleanup = null;
|
|
1838
|
+
}
|
|
1752
1839
|
this._readable.destroy(error);
|
|
1753
1840
|
this._writable.destroy(error);
|
|
1754
1841
|
queueMicrotask(() => this.emit("close"));
|
|
@@ -1757,7 +1844,44 @@ export class Transform extends EventEmitter {
|
|
|
1757
1844
|
* Get the underlying Web TransformStream
|
|
1758
1845
|
*/
|
|
1759
1846
|
get webStream() {
|
|
1760
|
-
|
|
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;
|
|
1761
1885
|
}
|
|
1762
1886
|
get readable() {
|
|
1763
1887
|
return this._readable.readable;
|
|
@@ -1799,19 +1923,6 @@ export class Transform extends EventEmitter {
|
|
|
1799
1923
|
* Async iterator support
|
|
1800
1924
|
*/
|
|
1801
1925
|
async *[Symbol.asyncIterator]() {
|
|
1802
|
-
// If auto-consume is running, wait for it to finish and use its buffer
|
|
1803
|
-
if (this._autoConsumePromise) {
|
|
1804
|
-
await this._autoConsumePromise;
|
|
1805
|
-
// Yield all buffered data
|
|
1806
|
-
while (this._autoConsumedBufferIndex < this._autoConsumedBuffer.length) {
|
|
1807
|
-
yield this._autoConsumedBuffer[this._autoConsumedBufferIndex++];
|
|
1808
|
-
}
|
|
1809
|
-
// Reset when drained to avoid prefix growth
|
|
1810
|
-
this._autoConsumedBuffer.length = 0;
|
|
1811
|
-
this._autoConsumedBufferIndex = 0;
|
|
1812
|
-
return;
|
|
1813
|
-
}
|
|
1814
|
-
// Otherwise delegate to readable's iterator
|
|
1815
1926
|
yield* this._readable[Symbol.asyncIterator]();
|
|
1816
1927
|
}
|
|
1817
1928
|
// =========================================================================
|
|
@@ -1822,23 +1933,18 @@ export class Transform extends EventEmitter {
|
|
|
1822
1933
|
*/
|
|
1823
1934
|
static fromWeb(webStream, options) {
|
|
1824
1935
|
const transform = new Transform(options);
|
|
1825
|
-
|
|
1826
|
-
transform._stream = webStream;
|
|
1936
|
+
transform._webStream = webStream;
|
|
1827
1937
|
// Replace internal streams with the ones from the web stream
|
|
1828
1938
|
const newReadable = Readable.fromWeb(webStream.readable, { objectMode: options?.objectMode });
|
|
1829
1939
|
const newWritable = Writable.fromWeb(webStream.writable, { objectMode: options?.objectMode });
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1940
|
+
if (transform._sideForwardingCleanup) {
|
|
1941
|
+
transform._sideForwardingCleanup();
|
|
1942
|
+
transform._sideForwardingCleanup = null;
|
|
1943
|
+
}
|
|
1833
1944
|
transform._readable = newReadable;
|
|
1834
1945
|
transform._writable = newWritable;
|
|
1835
|
-
// Re-connect event forwarding
|
|
1836
|
-
|
|
1837
|
-
newReadable.on("end", () => transform.emit("end"));
|
|
1838
|
-
newReadable.on("error", (err) => transform.emit("error", err));
|
|
1839
|
-
newWritable.on("finish", () => transform.emit("finish"));
|
|
1840
|
-
newWritable.on("drain", () => transform.emit("drain"));
|
|
1841
|
-
newWritable.on("error", (err) => transform.emit("error", err));
|
|
1946
|
+
// Re-connect event forwarding (data forwarding remains lazy via Transform.on)
|
|
1947
|
+
transform._setupSideForwarding();
|
|
1842
1948
|
return transform;
|
|
1843
1949
|
}
|
|
1844
1950
|
/**
|
|
@@ -1894,7 +2000,13 @@ export class Duplex extends EventEmitter {
|
|
|
1894
2000
|
callback();
|
|
1895
2001
|
}
|
|
1896
2002
|
});
|
|
1897
|
-
|
|
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 });
|
|
1898
2010
|
readable.pipe(sink);
|
|
1899
2011
|
};
|
|
1900
2012
|
// If it has readable and/or writable properties
|
|
@@ -1902,21 +2014,25 @@ export class Duplex extends EventEmitter {
|
|
|
1902
2014
|
source !== null &&
|
|
1903
2015
|
"readable" in source &&
|
|
1904
2016
|
"writable" in source) {
|
|
1905
|
-
const duplex = new Duplex();
|
|
1906
2017
|
const pair = source;
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
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) => {
|
|
1914
2025
|
pair.writable.write(chunk, encoding, callback);
|
|
1915
|
-
}
|
|
1916
|
-
|
|
2026
|
+
}
|
|
2027
|
+
: undefined,
|
|
2028
|
+
final: pair.writable
|
|
2029
|
+
? callback => {
|
|
1917
2030
|
pair.writable.end(callback);
|
|
1918
2031
|
}
|
|
1919
|
-
|
|
2032
|
+
: undefined
|
|
2033
|
+
});
|
|
2034
|
+
if (pair.readable) {
|
|
2035
|
+
forwardReadableToDuplex(pair.readable, duplex);
|
|
1920
2036
|
}
|
|
1921
2037
|
return duplex;
|
|
1922
2038
|
}
|
|
@@ -1954,9 +2070,22 @@ export class Duplex extends EventEmitter {
|
|
|
1954
2070
|
*/
|
|
1955
2071
|
static fromWeb(pair, options) {
|
|
1956
2072
|
const duplex = new Duplex(options);
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
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();
|
|
1960
2089
|
return duplex;
|
|
1961
2090
|
}
|
|
1962
2091
|
/**
|
|
@@ -1972,6 +2101,7 @@ export class Duplex extends EventEmitter {
|
|
|
1972
2101
|
super();
|
|
1973
2102
|
// Track if we've already set up data forwarding
|
|
1974
2103
|
this._dataForwardingSetup = false;
|
|
2104
|
+
this._sideForwardingCleanup = null;
|
|
1975
2105
|
this.allowHalfOpen = options?.allowHalfOpen ?? true;
|
|
1976
2106
|
// Support shorthand objectMode option
|
|
1977
2107
|
const objectMode = options?.objectMode ?? false;
|
|
@@ -1988,23 +2118,31 @@ export class Duplex extends EventEmitter {
|
|
|
1988
2118
|
write: options?.write?.bind(this),
|
|
1989
2119
|
final: options?.final?.bind(this)
|
|
1990
2120
|
});
|
|
2121
|
+
this._setupSideForwarding();
|
|
2122
|
+
}
|
|
2123
|
+
_setupSideForwarding() {
|
|
2124
|
+
if (this._sideForwardingCleanup) {
|
|
2125
|
+
this._sideForwardingCleanup();
|
|
2126
|
+
this._sideForwardingCleanup = null;
|
|
2127
|
+
}
|
|
2128
|
+
const registry = createListenerRegistry();
|
|
1991
2129
|
// Forward non-data events (data forwarding is lazy to avoid premature flowing)
|
|
1992
|
-
this._readable
|
|
2130
|
+
registry.once(this._readable, "end", () => {
|
|
1993
2131
|
this.emit("end");
|
|
1994
|
-
// If not allowHalfOpen, end the writable side too
|
|
1995
2132
|
if (!this.allowHalfOpen) {
|
|
1996
2133
|
this._writable.end();
|
|
1997
2134
|
}
|
|
1998
2135
|
});
|
|
1999
|
-
this._readable
|
|
2000
|
-
this._writable
|
|
2001
|
-
this._writable
|
|
2002
|
-
this._writable
|
|
2003
|
-
|
|
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", () => {
|
|
2004
2141
|
if (!this.allowHalfOpen && !this._readable.destroyed) {
|
|
2005
2142
|
this._readable.destroy();
|
|
2006
2143
|
}
|
|
2007
2144
|
});
|
|
2145
|
+
this._sideForwardingCleanup = () => registry.cleanup();
|
|
2008
2146
|
}
|
|
2009
2147
|
/**
|
|
2010
2148
|
* Override on() to set up data forwarding lazily
|
|
@@ -2123,6 +2261,10 @@ export class Duplex extends EventEmitter {
|
|
|
2123
2261
|
* Destroy both sides
|
|
2124
2262
|
*/
|
|
2125
2263
|
destroy(error) {
|
|
2264
|
+
if (this._sideForwardingCleanup) {
|
|
2265
|
+
this._sideForwardingCleanup();
|
|
2266
|
+
this._sideForwardingCleanup = null;
|
|
2267
|
+
}
|
|
2126
2268
|
this._readable.destroy(error);
|
|
2127
2269
|
this._writable.destroy(error);
|
|
2128
2270
|
return this;
|
|
@@ -2285,36 +2427,16 @@ export class BufferedStream extends StandaloneBufferedStream {
|
|
|
2285
2427
|
* Create a readable stream with custom read implementation
|
|
2286
2428
|
*/
|
|
2287
2429
|
export function createReadable(options) {
|
|
2288
|
-
|
|
2289
|
-
//
|
|
2290
|
-
|
|
2291
|
-
const originalRead = readable.read.bind(readable);
|
|
2292
|
-
readable.read = function (size) {
|
|
2293
|
-
options.read(size ?? 16384);
|
|
2294
|
-
return originalRead(size);
|
|
2295
|
-
};
|
|
2296
|
-
}
|
|
2297
|
-
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);
|
|
2298
2433
|
}
|
|
2299
2434
|
/**
|
|
2300
2435
|
* Create a readable stream from an async iterable
|
|
2301
2436
|
*/
|
|
2302
2437
|
export function createReadableFromAsyncIterable(iterable, options) {
|
|
2303
2438
|
const readable = new Readable({ ...options, objectMode: options?.objectMode ?? true });
|
|
2304
|
-
(
|
|
2305
|
-
try {
|
|
2306
|
-
for await (const chunk of iterable) {
|
|
2307
|
-
if (!readable.push(chunk)) {
|
|
2308
|
-
// Backpressure - wait a bit
|
|
2309
|
-
await new Promise(resolve => setTimeout(resolve, 0));
|
|
2310
|
-
}
|
|
2311
|
-
}
|
|
2312
|
-
readable.push(null);
|
|
2313
|
-
}
|
|
2314
|
-
catch (err) {
|
|
2315
|
-
readable.destroy(err);
|
|
2316
|
-
}
|
|
2317
|
-
})();
|
|
2439
|
+
pumpAsyncIterableToReadable(readable, iterable);
|
|
2318
2440
|
return readable;
|
|
2319
2441
|
}
|
|
2320
2442
|
/**
|
|
@@ -2343,38 +2465,8 @@ export function createReadableFromArray(data, options) {
|
|
|
2343
2465
|
* Create a writable stream with custom write implementation
|
|
2344
2466
|
*/
|
|
2345
2467
|
export function createWritable(options) {
|
|
2346
|
-
//
|
|
2347
|
-
|
|
2348
|
-
write: async (chunk) => {
|
|
2349
|
-
if (options?.write) {
|
|
2350
|
-
return new Promise((resolve, reject) => {
|
|
2351
|
-
options.write(chunk, "utf8", err => {
|
|
2352
|
-
if (err) {
|
|
2353
|
-
reject(err);
|
|
2354
|
-
}
|
|
2355
|
-
else {
|
|
2356
|
-
resolve();
|
|
2357
|
-
}
|
|
2358
|
-
});
|
|
2359
|
-
});
|
|
2360
|
-
}
|
|
2361
|
-
},
|
|
2362
|
-
close: async () => {
|
|
2363
|
-
if (options?.final) {
|
|
2364
|
-
return new Promise((resolve, reject) => {
|
|
2365
|
-
options.final(err => {
|
|
2366
|
-
if (err) {
|
|
2367
|
-
reject(err);
|
|
2368
|
-
}
|
|
2369
|
-
else {
|
|
2370
|
-
resolve();
|
|
2371
|
-
}
|
|
2372
|
-
});
|
|
2373
|
-
});
|
|
2374
|
-
}
|
|
2375
|
-
}
|
|
2376
|
-
});
|
|
2377
|
-
return new Writable({ ...options, stream });
|
|
2468
|
+
// Writable already supports Node-style `write()` / `final()` via the constructor.
|
|
2469
|
+
return new Writable(options);
|
|
2378
2470
|
}
|
|
2379
2471
|
/**
|
|
2380
2472
|
* Create a transform stream from a transform function
|
|
@@ -2509,11 +2601,17 @@ export function pipeline(...args) {
|
|
|
2509
2601
|
const transforms = normalized.slice(1, -1);
|
|
2510
2602
|
let completed = false;
|
|
2511
2603
|
const allStreams = [source, ...transforms, destination];
|
|
2512
|
-
const
|
|
2604
|
+
const registry = createListenerRegistry();
|
|
2605
|
+
let onAbort;
|
|
2606
|
+
const cleanupWithSignal = (error) => {
|
|
2513
2607
|
if (completed) {
|
|
2514
2608
|
return;
|
|
2515
2609
|
}
|
|
2516
2610
|
completed = true;
|
|
2611
|
+
registry.cleanup();
|
|
2612
|
+
if (onAbort && options.signal) {
|
|
2613
|
+
options.signal.removeEventListener("abort", onAbort);
|
|
2614
|
+
}
|
|
2517
2615
|
// Destroy all streams on error
|
|
2518
2616
|
if (error) {
|
|
2519
2617
|
for (const stream of allStreams) {
|
|
@@ -2530,12 +2628,11 @@ export function pipeline(...args) {
|
|
|
2530
2628
|
// Handle abort signal
|
|
2531
2629
|
if (options.signal) {
|
|
2532
2630
|
if (options.signal.aborted) {
|
|
2533
|
-
|
|
2631
|
+
cleanupWithSignal(new Error("Pipeline aborted"));
|
|
2534
2632
|
return;
|
|
2535
2633
|
}
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
});
|
|
2634
|
+
onAbort = () => cleanupWithSignal(new Error("Pipeline aborted"));
|
|
2635
|
+
options.signal.addEventListener("abort", onAbort);
|
|
2539
2636
|
}
|
|
2540
2637
|
// Chain the streams
|
|
2541
2638
|
let current = source;
|
|
@@ -2549,13 +2646,35 @@ export function pipeline(...args) {
|
|
|
2549
2646
|
}
|
|
2550
2647
|
else {
|
|
2551
2648
|
// Don't end destination
|
|
2552
|
-
|
|
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);
|
|
2553
2672
|
}
|
|
2554
2673
|
// Handle completion
|
|
2555
|
-
|
|
2674
|
+
registry.once(destination, "finish", () => cleanupWithSignal());
|
|
2556
2675
|
// Handle errors on all streams
|
|
2557
2676
|
for (const stream of allStreams) {
|
|
2558
|
-
|
|
2677
|
+
registry.once(stream, "error", (err) => cleanupWithSignal(err));
|
|
2559
2678
|
}
|
|
2560
2679
|
});
|
|
2561
2680
|
// If callback provided, use it
|
|
@@ -2595,11 +2714,20 @@ export function finished(stream, optionsOrCallback, callback) {
|
|
|
2595
2714
|
const promise = new Promise((resolve, reject) => {
|
|
2596
2715
|
const normalizedStream = toBrowserPipelineStream(stream);
|
|
2597
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
|
+
};
|
|
2598
2725
|
const done = (err) => {
|
|
2599
2726
|
if (resolved) {
|
|
2600
2727
|
return;
|
|
2601
2728
|
}
|
|
2602
2729
|
resolved = true;
|
|
2730
|
+
cleanup();
|
|
2603
2731
|
if (err && !options.error) {
|
|
2604
2732
|
reject(err);
|
|
2605
2733
|
}
|
|
@@ -2613,9 +2741,8 @@ export function finished(stream, optionsOrCallback, callback) {
|
|
|
2613
2741
|
done(new Error("Aborted"));
|
|
2614
2742
|
return;
|
|
2615
2743
|
}
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
});
|
|
2744
|
+
onAbort = () => done(new Error("Aborted"));
|
|
2745
|
+
options.signal.addEventListener("abort", onAbort);
|
|
2619
2746
|
}
|
|
2620
2747
|
const checkReadable = options.readable !== false;
|
|
2621
2748
|
const checkWritable = options.writable !== false;
|
|
@@ -2630,13 +2757,13 @@ export function finished(stream, optionsOrCallback, callback) {
|
|
|
2630
2757
|
}
|
|
2631
2758
|
// Listen for events
|
|
2632
2759
|
if (checkWritable) {
|
|
2633
|
-
|
|
2760
|
+
registry.once(normalizedStream, "finish", () => done());
|
|
2634
2761
|
}
|
|
2635
2762
|
if (checkReadable) {
|
|
2636
|
-
|
|
2763
|
+
registry.once(normalizedStream, "end", () => done());
|
|
2637
2764
|
}
|
|
2638
|
-
|
|
2639
|
-
|
|
2765
|
+
registry.once(normalizedStream, "error", (err) => done(err));
|
|
2766
|
+
registry.once(normalizedStream, "close", () => done());
|
|
2640
2767
|
});
|
|
2641
2768
|
// If callback provided, use it
|
|
2642
2769
|
if (cb) {
|
|
@@ -2658,38 +2785,8 @@ export async function streamToPromise(stream) {
|
|
|
2658
2785
|
* (Browser equivalent of Node.js streamToBuffer)
|
|
2659
2786
|
*/
|
|
2660
2787
|
export async function streamToUint8Array(stream) {
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
iterable = Readable.fromWeb(stream);
|
|
2664
|
-
}
|
|
2665
|
-
else if (isAsyncIterable(stream)) {
|
|
2666
|
-
iterable = stream;
|
|
2667
|
-
}
|
|
2668
|
-
else {
|
|
2669
|
-
throw new Error("streamToUint8Array: unsupported stream type");
|
|
2670
|
-
}
|
|
2671
|
-
const chunks = [];
|
|
2672
|
-
let totalLength = 0;
|
|
2673
|
-
for await (const chunk of iterable) {
|
|
2674
|
-
chunks.push(chunk);
|
|
2675
|
-
totalLength += chunk.length;
|
|
2676
|
-
}
|
|
2677
|
-
// Fast paths
|
|
2678
|
-
const len = chunks.length;
|
|
2679
|
-
if (len === 0) {
|
|
2680
|
-
return new Uint8Array(0);
|
|
2681
|
-
}
|
|
2682
|
-
if (len === 1) {
|
|
2683
|
-
return chunks[0];
|
|
2684
|
-
}
|
|
2685
|
-
// Use precalculated total length
|
|
2686
|
-
const result = new Uint8Array(totalLength);
|
|
2687
|
-
let offset = 0;
|
|
2688
|
-
for (let i = 0; i < len; i++) {
|
|
2689
|
-
result.set(chunks[i], offset);
|
|
2690
|
-
offset += chunks[i].length;
|
|
2691
|
-
}
|
|
2692
|
-
return result;
|
|
2788
|
+
const { chunks, totalLength } = await collectStreamChunks(stream);
|
|
2789
|
+
return concatWithLength(chunks, totalLength);
|
|
2693
2790
|
}
|
|
2694
2791
|
/**
|
|
2695
2792
|
* Alias for streamToUint8Array (Node.js compatibility)
|
|
@@ -2699,8 +2796,10 @@ export const streamToBuffer = streamToUint8Array;
|
|
|
2699
2796
|
* Collect all data from a readable stream into a string
|
|
2700
2797
|
*/
|
|
2701
2798
|
export async function streamToString(stream, encoding) {
|
|
2702
|
-
const
|
|
2703
|
-
|
|
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);
|
|
2704
2803
|
}
|
|
2705
2804
|
/**
|
|
2706
2805
|
* Drain a stream (consume all data without processing)
|
|
@@ -2788,14 +2887,19 @@ export function addAbortSignal(signal, stream) {
|
|
|
2788
2887
|
stream.destroy(new Error("Aborted"));
|
|
2789
2888
|
return stream;
|
|
2790
2889
|
}
|
|
2890
|
+
const cleanup = () => {
|
|
2891
|
+
signal.removeEventListener("abort", onAbort);
|
|
2892
|
+
removeEmitterListener(stream, "close", onClose);
|
|
2893
|
+
};
|
|
2791
2894
|
const onAbort = () => {
|
|
2895
|
+
cleanup();
|
|
2792
2896
|
stream.destroy(new Error("Aborted"));
|
|
2793
2897
|
};
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
});
|
|
2898
|
+
const onClose = () => {
|
|
2899
|
+
cleanup();
|
|
2900
|
+
};
|
|
2901
|
+
signal.addEventListener("abort", onAbort);
|
|
2902
|
+
addEmitterListener(stream, "close", onClose, { once: true });
|
|
2799
2903
|
return stream;
|
|
2800
2904
|
}
|
|
2801
2905
|
/**
|
|
@@ -2804,60 +2908,68 @@ export function addAbortSignal(signal, stream) {
|
|
|
2804
2908
|
export function createDuplex(options) {
|
|
2805
2909
|
const readableObjectMode = options?.readableObjectMode ?? options?.objectMode;
|
|
2806
2910
|
const writableObjectMode = options?.writableObjectMode ?? options?.objectMode;
|
|
2911
|
+
const underlyingWritable = options?.writable;
|
|
2807
2912
|
const duplex = new Duplex({
|
|
2808
2913
|
allowHalfOpen: options?.allowHalfOpen,
|
|
2809
2914
|
readableHighWaterMark: options?.readableHighWaterMark,
|
|
2810
2915
|
writableHighWaterMark: options?.writableHighWaterMark,
|
|
2811
2916
|
readableObjectMode,
|
|
2812
|
-
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)
|
|
2813
2948
|
});
|
|
2814
|
-
// If
|
|
2949
|
+
// If an underlying readable is provided, forward it into the duplex readable side.
|
|
2815
2950
|
if (options?.readable) {
|
|
2816
2951
|
const readable = options.readable;
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
duplex.end = function (chunkOrCallback, encodingOrCallback, callback) {
|
|
2839
|
-
const cb = typeof chunkOrCallback === "function"
|
|
2840
|
-
? chunkOrCallback
|
|
2841
|
-
: typeof encodingOrCallback === "function"
|
|
2842
|
-
? encodingOrCallback
|
|
2843
|
-
: (callback ?? (() => { }));
|
|
2844
|
-
if (chunkOrCallback !== undefined && typeof chunkOrCallback !== "function") {
|
|
2845
|
-
duplex.write(chunkOrCallback);
|
|
2846
|
-
}
|
|
2847
|
-
// Call custom final handler
|
|
2848
|
-
options.final.call(duplex, (err) => {
|
|
2849
|
-
if (err) {
|
|
2850
|
-
duplex.emit("error", err);
|
|
2851
|
-
}
|
|
2852
|
-
else {
|
|
2853
|
-
duplex.emit("finish");
|
|
2854
|
-
}
|
|
2855
|
-
// Call original end to properly close writable side
|
|
2856
|
-
originalEnd();
|
|
2857
|
-
cb();
|
|
2858
|
-
});
|
|
2859
|
-
return duplex;
|
|
2860
|
-
};
|
|
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);
|
|
2861
2973
|
}
|
|
2862
2974
|
if (options?.destroy) {
|
|
2863
2975
|
const originalDestroy = duplex.destroy.bind(duplex);
|
|
@@ -2881,20 +2993,7 @@ export function createDuplex(options) {
|
|
|
2881
2993
|
*/
|
|
2882
2994
|
export function createReadableFromGenerator(generator, options) {
|
|
2883
2995
|
const readable = new Readable({ ...options, objectMode: options?.objectMode ?? true });
|
|
2884
|
-
(
|
|
2885
|
-
try {
|
|
2886
|
-
for await (const chunk of generator()) {
|
|
2887
|
-
if (!readable.push(chunk)) {
|
|
2888
|
-
// Backpressure
|
|
2889
|
-
await new Promise(resolve => setTimeout(resolve, 0));
|
|
2890
|
-
}
|
|
2891
|
-
}
|
|
2892
|
-
readable.push(null);
|
|
2893
|
-
}
|
|
2894
|
-
catch (err) {
|
|
2895
|
-
readable.destroy(err);
|
|
2896
|
-
}
|
|
2897
|
-
})();
|
|
2996
|
+
pumpAsyncIterableToReadable(readable, generator());
|
|
2898
2997
|
return readable;
|
|
2899
2998
|
}
|
|
2900
2999
|
/**
|
|
@@ -2924,8 +3023,8 @@ export function compose(...transforms) {
|
|
|
2924
3023
|
transform: chunk => chunk
|
|
2925
3024
|
});
|
|
2926
3025
|
}
|
|
2927
|
-
|
|
2928
|
-
if (len === 1
|
|
3026
|
+
// Preserve identity: compose(single) returns the same transform.
|
|
3027
|
+
if (len === 1) {
|
|
2929
3028
|
return transforms[0];
|
|
2930
3029
|
}
|
|
2931
3030
|
// Chain the transforms: first → second → ... → last
|
|
@@ -2936,19 +3035,34 @@ export function compose(...transforms) {
|
|
|
2936
3035
|
transforms[i].pipe(transforms[i + 1]);
|
|
2937
3036
|
}
|
|
2938
3037
|
class ComposedTransform extends Transform {
|
|
2939
|
-
constructor() {
|
|
2940
|
-
super(
|
|
3038
|
+
constructor(options) {
|
|
3039
|
+
super(options);
|
|
2941
3040
|
this._dataForwarding = false;
|
|
2942
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
|
+
}
|
|
2943
3051
|
}
|
|
2944
3052
|
on(event, listener) {
|
|
2945
3053
|
if (event === "data" && !this._dataForwarding) {
|
|
2946
3054
|
this._dataForwarding = true;
|
|
2947
|
-
|
|
3055
|
+
const onData = (chunk) => {
|
|
3056
|
+
this.emit("data", chunk);
|
|
3057
|
+
};
|
|
3058
|
+
this._dataForwardCleanup = addEmitterListener(last, "data", onData);
|
|
2948
3059
|
}
|
|
2949
3060
|
if (event === "end" && !this._endForwarding) {
|
|
2950
3061
|
this._endForwarding = true;
|
|
2951
|
-
|
|
3062
|
+
const onEnd = () => {
|
|
3063
|
+
this.emit("end");
|
|
3064
|
+
};
|
|
3065
|
+
this._endForwardCleanup = addEmitterListener(last, "end", onEnd, { once: true });
|
|
2952
3066
|
}
|
|
2953
3067
|
return super.on(event, listener);
|
|
2954
3068
|
}
|
|
@@ -2974,6 +3088,18 @@ export function compose(...transforms) {
|
|
|
2974
3088
|
return last.pipe(destination);
|
|
2975
3089
|
}
|
|
2976
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;
|
|
2977
3103
|
for (const t of transforms) {
|
|
2978
3104
|
t.destroy(error);
|
|
2979
3105
|
}
|
|
@@ -2997,12 +3123,6 @@ export function compose(...transforms) {
|
|
|
2997
3123
|
objectMode: first?.objectMode ?? true,
|
|
2998
3124
|
transform: chunk => chunk
|
|
2999
3125
|
});
|
|
3000
|
-
// Forward errors from any transform
|
|
3001
|
-
for (const t of transforms) {
|
|
3002
|
-
t.on("error", (err) => {
|
|
3003
|
-
composed.emit("error", err);
|
|
3004
|
-
});
|
|
3005
|
-
}
|
|
3006
3126
|
// Reflect underlying readability/writability like the previous duck-typed wrapper
|
|
3007
3127
|
Object.defineProperty(composed, "readable", {
|
|
3008
3128
|
get: () => last.readable
|