@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
|
@@ -55,6 +55,40 @@ const event_emitter_1 = require("./event-emitter.js");
|
|
|
55
55
|
const pull_stream_1 = require("./pull-stream.js");
|
|
56
56
|
const buffered_stream_1 = require("./buffered-stream.js");
|
|
57
57
|
const shared_1 = require("./shared.js");
|
|
58
|
+
const removeEmitterListener = (emitter, event, listener) => {
|
|
59
|
+
if (typeof emitter.off === "function") {
|
|
60
|
+
emitter.off(event, listener);
|
|
61
|
+
}
|
|
62
|
+
else if (typeof emitter.removeListener === "function") {
|
|
63
|
+
emitter.removeListener(event, listener);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const addEmitterListener = (emitter, event, listener, options) => {
|
|
67
|
+
if (options?.once && typeof emitter.once === "function") {
|
|
68
|
+
emitter.once(event, listener);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
emitter.on(event, listener);
|
|
72
|
+
}
|
|
73
|
+
return () => removeEmitterListener(emitter, event, listener);
|
|
74
|
+
};
|
|
75
|
+
const createListenerRegistry = () => {
|
|
76
|
+
const listeners = [];
|
|
77
|
+
return {
|
|
78
|
+
add: (emitter, event, listener) => {
|
|
79
|
+
listeners.push(addEmitterListener(emitter, event, listener));
|
|
80
|
+
},
|
|
81
|
+
once: (emitter, event, listener) => {
|
|
82
|
+
listeners.push(addEmitterListener(emitter, event, listener, { once: true }));
|
|
83
|
+
},
|
|
84
|
+
cleanup: () => {
|
|
85
|
+
for (let i = listeners.length - 1; i >= 0; i--) {
|
|
86
|
+
listeners[i]();
|
|
87
|
+
}
|
|
88
|
+
listeners.length = 0;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
};
|
|
58
92
|
// =============================================================================
|
|
59
93
|
// Readable Stream Wrapper
|
|
60
94
|
// =============================================================================
|
|
@@ -71,6 +105,7 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
71
105
|
this._bufferSize = 0;
|
|
72
106
|
this._reading = false;
|
|
73
107
|
this._ended = false;
|
|
108
|
+
this._endEmitted = false;
|
|
74
109
|
this._destroyed = false;
|
|
75
110
|
this._errored = null;
|
|
76
111
|
this._closed = false;
|
|
@@ -127,20 +162,7 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
127
162
|
*/
|
|
128
163
|
static from(iterable, options) {
|
|
129
164
|
const readable = new Readable({ ...options, objectMode: options?.objectMode ?? true });
|
|
130
|
-
(
|
|
131
|
-
try {
|
|
132
|
-
for await (const chunk of iterable) {
|
|
133
|
-
if (!readable.push(chunk)) {
|
|
134
|
-
// Backpressure
|
|
135
|
-
await new Promise(resolve => setTimeout(resolve, 0));
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
readable.push(null);
|
|
139
|
-
}
|
|
140
|
-
catch (err) {
|
|
141
|
-
readable.destroy(err);
|
|
142
|
-
}
|
|
143
|
-
})();
|
|
165
|
+
pumpAsyncIterableToReadable(readable, toAsyncIterable(iterable));
|
|
144
166
|
return readable;
|
|
145
167
|
}
|
|
146
168
|
/**
|
|
@@ -185,16 +207,30 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
185
207
|
// Controller may already be closed
|
|
186
208
|
}
|
|
187
209
|
}
|
|
188
|
-
|
|
210
|
+
// Emit 'end' only after buffered data is fully drained.
|
|
211
|
+
// This avoids premature 'end' when producers push null while paused.
|
|
212
|
+
if (this._bufferedLength() === 0) {
|
|
213
|
+
this._emitEndOnce();
|
|
214
|
+
}
|
|
189
215
|
// Note: Don't call destroy() here, let the stream be consumed naturally
|
|
190
216
|
// The reader will return done:true when it finishes reading
|
|
191
217
|
return false;
|
|
192
218
|
}
|
|
219
|
+
// Keep the internal Web ReadableStream in sync for controllable streams.
|
|
220
|
+
// For external Web streams (_webStreamMode=true), push() is not the data source.
|
|
221
|
+
if (controller && !this._webStreamMode) {
|
|
222
|
+
try {
|
|
223
|
+
controller.enqueue(chunk);
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
// Controller may be closed/errored; Node-side buffering/events still work.
|
|
227
|
+
}
|
|
228
|
+
}
|
|
193
229
|
if (this._flowing) {
|
|
194
230
|
// In flowing mode, emit data directly without buffering or enqueueing
|
|
195
231
|
// const chunkStr = chunk instanceof Uint8Array ? new TextDecoder().decode(chunk.slice(0, 50)) : String(chunk).slice(0, 50);
|
|
196
232
|
// console.log(`[Readable#${this._id}.push FLOWING] emit data size:${(chunk as any).length || (chunk as any).byteLength} start:"${chunkStr}"`);
|
|
197
|
-
this.emit("data", chunk);
|
|
233
|
+
this.emit("data", this._applyEncoding(chunk));
|
|
198
234
|
// Check if stream was paused during emit (backpressure from consumer)
|
|
199
235
|
if (!this._flowing) {
|
|
200
236
|
return false;
|
|
@@ -219,10 +255,8 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
219
255
|
if (!this.objectMode) {
|
|
220
256
|
this._bufferSize += this._getChunkSize(chunk);
|
|
221
257
|
}
|
|
222
|
-
// NOTE:
|
|
223
|
-
//
|
|
224
|
-
// Web Stream is only used for async iteration when not in push mode.
|
|
225
|
-
// Enqueueing here would cause data duplication when _startReading is also running.
|
|
258
|
+
// NOTE: We still buffer for Node-style read()/data semantics.
|
|
259
|
+
// The internal Web ReadableStream is also fed via controller.enqueue() above.
|
|
226
260
|
// Emit readable event when buffer goes from empty to having data
|
|
227
261
|
if (wasEmpty) {
|
|
228
262
|
queueMicrotask(() => this.emit("readable"));
|
|
@@ -236,6 +270,13 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
236
270
|
return this._bufferSize < this.readableHighWaterMark;
|
|
237
271
|
}
|
|
238
272
|
}
|
|
273
|
+
_emitEndOnce() {
|
|
274
|
+
if (this._endEmitted) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
this._endEmitted = true;
|
|
278
|
+
this.emit("end");
|
|
279
|
+
}
|
|
239
280
|
/**
|
|
240
281
|
* Put a chunk back at the front of the buffer
|
|
241
282
|
* Note: unshift is allowed even after end, as it's used to put back already read data
|
|
@@ -260,14 +301,22 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
260
301
|
if (!this.objectMode) {
|
|
261
302
|
this._bufferSize -= this._getChunkSize(chunk);
|
|
262
303
|
}
|
|
263
|
-
|
|
304
|
+
const decoded = this._applyEncoding(chunk);
|
|
305
|
+
if (this._ended && this._bufferedLength() === 0) {
|
|
306
|
+
queueMicrotask(() => this._emitEndOnce());
|
|
307
|
+
}
|
|
308
|
+
return decoded;
|
|
264
309
|
}
|
|
265
310
|
// For binary mode, handle size
|
|
266
311
|
const chunk = this._bufferShift();
|
|
267
312
|
if (!this.objectMode) {
|
|
268
313
|
this._bufferSize -= this._getChunkSize(chunk);
|
|
269
314
|
}
|
|
270
|
-
|
|
315
|
+
const decoded = this._applyEncoding(chunk);
|
|
316
|
+
if (this._ended && this._bufferedLength() === 0) {
|
|
317
|
+
queueMicrotask(() => this._emitEndOnce());
|
|
318
|
+
}
|
|
319
|
+
return decoded;
|
|
271
320
|
}
|
|
272
321
|
return null;
|
|
273
322
|
}
|
|
@@ -373,11 +422,11 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
373
422
|
if (!this.objectMode) {
|
|
374
423
|
this._bufferSize -= this._getChunkSize(chunk);
|
|
375
424
|
}
|
|
376
|
-
this.emit("data", chunk);
|
|
425
|
+
this.emit("data", this._applyEncoding(chunk));
|
|
377
426
|
}
|
|
378
427
|
// If already ended, emit end event
|
|
379
428
|
if (this._ended && this._bufferedLength() === 0) {
|
|
380
|
-
this.
|
|
429
|
+
this._emitEndOnce();
|
|
381
430
|
}
|
|
382
431
|
else if (this._read) {
|
|
383
432
|
// Call user-provided read function asynchronously
|
|
@@ -440,26 +489,36 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
440
489
|
}
|
|
441
490
|
this._pipeTo.push(dest);
|
|
442
491
|
// Create listeners that we can later remove
|
|
492
|
+
let drainListener;
|
|
493
|
+
const removeDrainListener = () => {
|
|
494
|
+
if (!drainListener) {
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
if (typeof eventTarget.off === "function") {
|
|
498
|
+
eventTarget.off("drain", drainListener);
|
|
499
|
+
}
|
|
500
|
+
else if (typeof eventTarget.removeListener === "function") {
|
|
501
|
+
eventTarget.removeListener("drain", drainListener);
|
|
502
|
+
}
|
|
503
|
+
drainListener = undefined;
|
|
504
|
+
};
|
|
443
505
|
const dataListener = (chunk) => {
|
|
444
506
|
// Call destination's write() method (not internal _writable.write())
|
|
445
507
|
// This ensures Transform.write() logic runs properly
|
|
446
508
|
const canWrite = dest.write(chunk);
|
|
447
509
|
if (!canWrite) {
|
|
448
510
|
this.pause();
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
const resumeOnce = () => {
|
|
454
|
-
if (typeof eventTarget.off === "function") {
|
|
455
|
-
eventTarget.off("drain", resumeOnce);
|
|
456
|
-
}
|
|
457
|
-
else if (typeof eventTarget.removeListener === "function") {
|
|
458
|
-
eventTarget.removeListener("drain", resumeOnce);
|
|
459
|
-
}
|
|
511
|
+
// Install a removable, once-style drain listener.
|
|
512
|
+
if (!drainListener) {
|
|
513
|
+
drainListener = () => {
|
|
514
|
+
removeDrainListener();
|
|
460
515
|
this.resume();
|
|
461
516
|
};
|
|
462
|
-
eventTarget.on("drain",
|
|
517
|
+
eventTarget.on("drain", drainListener);
|
|
518
|
+
const entry = this._pipeListeners.get(dest);
|
|
519
|
+
if (entry) {
|
|
520
|
+
entry.drain = drainListener;
|
|
521
|
+
}
|
|
463
522
|
}
|
|
464
523
|
}
|
|
465
524
|
};
|
|
@@ -479,7 +538,8 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
479
538
|
this._pipeListeners.set(dest, {
|
|
480
539
|
data: dataListener,
|
|
481
540
|
end: endListener,
|
|
482
|
-
error: errorListener
|
|
541
|
+
error: errorListener,
|
|
542
|
+
eventTarget
|
|
483
543
|
});
|
|
484
544
|
this.on("data", dataListener);
|
|
485
545
|
this.once("end", endListener);
|
|
@@ -502,6 +562,14 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
502
562
|
this.off("data", listeners.data);
|
|
503
563
|
this.off("end", listeners.end);
|
|
504
564
|
this.off("error", listeners.error);
|
|
565
|
+
if (listeners.drain) {
|
|
566
|
+
if (typeof listeners.eventTarget?.off === "function") {
|
|
567
|
+
listeners.eventTarget.off("drain", listeners.drain);
|
|
568
|
+
}
|
|
569
|
+
else if (typeof listeners.eventTarget?.removeListener === "function") {
|
|
570
|
+
listeners.eventTarget.removeListener("drain", listeners.drain);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
505
573
|
this._pipeListeners.delete(destination);
|
|
506
574
|
}
|
|
507
575
|
}
|
|
@@ -513,6 +581,14 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
513
581
|
this.off("data", listeners.data);
|
|
514
582
|
this.off("end", listeners.end);
|
|
515
583
|
this.off("error", listeners.error);
|
|
584
|
+
if (listeners.drain) {
|
|
585
|
+
if (typeof listeners.eventTarget?.off === "function") {
|
|
586
|
+
listeners.eventTarget.off("drain", listeners.drain);
|
|
587
|
+
}
|
|
588
|
+
else if (typeof listeners.eventTarget?.removeListener === "function") {
|
|
589
|
+
listeners.eventTarget.removeListener("drain", listeners.drain);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
516
592
|
this._pipeListeners.delete(target);
|
|
517
593
|
}
|
|
518
594
|
}
|
|
@@ -531,12 +607,26 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
531
607
|
}
|
|
532
608
|
this._destroyed = true;
|
|
533
609
|
this._ended = true;
|
|
610
|
+
// Ensure we detach from destinations to avoid leaking listeners.
|
|
611
|
+
this.unpipe();
|
|
534
612
|
if (error) {
|
|
535
613
|
this._errored = error;
|
|
536
614
|
this.emit("error", error);
|
|
537
615
|
}
|
|
538
616
|
if (this._reader) {
|
|
539
|
-
this._reader
|
|
617
|
+
const reader = this._reader;
|
|
618
|
+
this._reader = null;
|
|
619
|
+
reader
|
|
620
|
+
.cancel()
|
|
621
|
+
.catch(() => { })
|
|
622
|
+
.finally(() => {
|
|
623
|
+
try {
|
|
624
|
+
reader.releaseLock();
|
|
625
|
+
}
|
|
626
|
+
catch {
|
|
627
|
+
// Ignore if a read is still pending
|
|
628
|
+
}
|
|
629
|
+
});
|
|
540
630
|
}
|
|
541
631
|
this._closed = true;
|
|
542
632
|
this.emit("close");
|
|
@@ -615,18 +705,38 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
615
705
|
const { done, value } = await this._reader.read();
|
|
616
706
|
// Check _pushMode again after async read - if push() was called, stop reading
|
|
617
707
|
if (this._pushMode) {
|
|
708
|
+
if (this._reader) {
|
|
709
|
+
const reader = this._reader;
|
|
710
|
+
this._reader = null;
|
|
711
|
+
try {
|
|
712
|
+
reader.releaseLock();
|
|
713
|
+
}
|
|
714
|
+
catch {
|
|
715
|
+
// Ignore if a read is still pending
|
|
716
|
+
}
|
|
717
|
+
}
|
|
618
718
|
break;
|
|
619
719
|
}
|
|
620
720
|
if (done) {
|
|
621
721
|
this._ended = true;
|
|
622
|
-
this.
|
|
722
|
+
this._emitEndOnce();
|
|
723
|
+
if (this._reader) {
|
|
724
|
+
const reader = this._reader;
|
|
725
|
+
this._reader = null;
|
|
726
|
+
try {
|
|
727
|
+
reader.releaseLock();
|
|
728
|
+
}
|
|
729
|
+
catch {
|
|
730
|
+
// Ignore if a read is still pending
|
|
731
|
+
}
|
|
732
|
+
}
|
|
623
733
|
break;
|
|
624
734
|
}
|
|
625
735
|
if (value !== undefined) {
|
|
626
736
|
// In flowing mode, emit data directly without buffering
|
|
627
737
|
// Only buffer if not flowing (paused mode)
|
|
628
738
|
if (this._flowing) {
|
|
629
|
-
this.emit("data", value);
|
|
739
|
+
this.emit("data", this._applyEncoding(value));
|
|
630
740
|
}
|
|
631
741
|
else {
|
|
632
742
|
this._buffer.push(value);
|
|
@@ -639,6 +749,16 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
639
749
|
}
|
|
640
750
|
catch (err) {
|
|
641
751
|
this.emit("error", err);
|
|
752
|
+
if (this._reader) {
|
|
753
|
+
const reader = this._reader;
|
|
754
|
+
this._reader = null;
|
|
755
|
+
try {
|
|
756
|
+
reader.releaseLock();
|
|
757
|
+
}
|
|
758
|
+
catch {
|
|
759
|
+
// Ignore if a read is still pending
|
|
760
|
+
}
|
|
761
|
+
}
|
|
642
762
|
}
|
|
643
763
|
finally {
|
|
644
764
|
this._reading = false;
|
|
@@ -646,7 +766,8 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
646
766
|
}
|
|
647
767
|
/**
|
|
648
768
|
* Async iterator support
|
|
649
|
-
* Uses
|
|
769
|
+
* Uses a unified event-queue iterator with simple backpressure.
|
|
770
|
+
* This matches Node's behavior more closely (iterator drives flowing mode).
|
|
650
771
|
*/
|
|
651
772
|
async *[Symbol.asyncIterator]() {
|
|
652
773
|
// First yield any buffered data
|
|
@@ -655,117 +776,124 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
655
776
|
if (!this.objectMode) {
|
|
656
777
|
this._bufferSize -= this._getChunkSize(chunk);
|
|
657
778
|
}
|
|
658
|
-
yield chunk;
|
|
779
|
+
yield this._applyEncoding(chunk);
|
|
659
780
|
}
|
|
660
|
-
// If already ended, we're done
|
|
661
781
|
if (this._ended) {
|
|
662
782
|
return;
|
|
663
783
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
let resolveNext = null;
|
|
670
|
-
let rejectNext = null;
|
|
671
|
-
let done = false;
|
|
672
|
-
let streamError = null;
|
|
673
|
-
let dataQueueIndex = 0;
|
|
674
|
-
const dataHandler = (chunk) => {
|
|
675
|
-
if (resolveNext) {
|
|
676
|
-
resolveNext(chunk);
|
|
677
|
-
resolveNext = null;
|
|
678
|
-
rejectNext = null;
|
|
679
|
-
}
|
|
680
|
-
else {
|
|
681
|
-
dataQueue.push(chunk);
|
|
682
|
-
}
|
|
683
|
-
};
|
|
684
|
-
const endHandler = () => {
|
|
685
|
-
done = true;
|
|
686
|
-
if (resolveNext) {
|
|
687
|
-
resolveNext(null);
|
|
688
|
-
resolveNext = null;
|
|
689
|
-
rejectNext = null;
|
|
690
|
-
}
|
|
691
|
-
};
|
|
692
|
-
const errorHandler = (err) => {
|
|
693
|
-
done = true;
|
|
694
|
-
streamError = err;
|
|
695
|
-
if (rejectNext) {
|
|
696
|
-
rejectNext(err);
|
|
697
|
-
resolveNext = null;
|
|
698
|
-
rejectNext = null;
|
|
699
|
-
}
|
|
700
|
-
};
|
|
701
|
-
const closeHandler = () => {
|
|
702
|
-
// If stream closed without end event (e.g., after destroy()),
|
|
703
|
-
// treat it as done
|
|
704
|
-
done = true;
|
|
705
|
-
if (resolveNext) {
|
|
706
|
-
resolveNext(null);
|
|
707
|
-
resolveNext = null;
|
|
708
|
-
rejectNext = null;
|
|
709
|
-
}
|
|
710
|
-
};
|
|
711
|
-
this.on("data", dataHandler);
|
|
712
|
-
this.on("end", endHandler);
|
|
713
|
-
this.on("error", errorHandler);
|
|
714
|
-
this.on("close", closeHandler);
|
|
715
|
-
try {
|
|
716
|
-
// Enter flowing mode
|
|
717
|
-
this.resume();
|
|
718
|
-
while (!done || dataQueueIndex < dataQueue.length) {
|
|
719
|
-
// Check for error before processing
|
|
720
|
-
if (streamError) {
|
|
721
|
-
throw streamError;
|
|
722
|
-
}
|
|
723
|
-
if (dataQueueIndex < dataQueue.length) {
|
|
724
|
-
const chunk = dataQueue[dataQueueIndex++];
|
|
725
|
-
if (dataQueueIndex >= 1024 && dataQueueIndex * 2 >= dataQueue.length) {
|
|
726
|
-
dataQueue.splice(0, dataQueueIndex);
|
|
727
|
-
dataQueueIndex = 0;
|
|
728
|
-
}
|
|
729
|
-
yield chunk;
|
|
730
|
-
}
|
|
731
|
-
else if (!done) {
|
|
732
|
-
const chunk = await new Promise((resolve, reject) => {
|
|
733
|
-
resolveNext = resolve;
|
|
734
|
-
rejectNext = reject;
|
|
735
|
-
});
|
|
736
|
-
if (chunk !== null) {
|
|
737
|
-
yield chunk;
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
// Check for error after loop
|
|
742
|
-
if (streamError) {
|
|
743
|
-
throw streamError;
|
|
744
|
-
}
|
|
784
|
+
const highWaterMark = this.readableHighWaterMark;
|
|
785
|
+
const lowWaterMark = Math.max(0, Math.floor(highWaterMark / 2));
|
|
786
|
+
const chunkSizeForBackpressure = (chunk) => {
|
|
787
|
+
if (this.objectMode) {
|
|
788
|
+
return 1;
|
|
745
789
|
}
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
this.off("end", endHandler);
|
|
749
|
-
this.off("error", errorHandler);
|
|
750
|
-
this.off("close", closeHandler);
|
|
790
|
+
if (chunk instanceof Uint8Array) {
|
|
791
|
+
return chunk.byteLength;
|
|
751
792
|
}
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
793
|
+
if (typeof chunk === "string") {
|
|
794
|
+
return chunk.length;
|
|
795
|
+
}
|
|
796
|
+
return 1;
|
|
797
|
+
};
|
|
798
|
+
const dataQueue = [];
|
|
799
|
+
let dataQueueIndex = 0;
|
|
800
|
+
let queuedSize = 0;
|
|
801
|
+
let resolveNext = null;
|
|
802
|
+
let rejectNext = null;
|
|
803
|
+
let done = false;
|
|
804
|
+
let pausedByIterator = false;
|
|
805
|
+
let streamError = null;
|
|
806
|
+
const dataHandler = (chunk) => {
|
|
807
|
+
// data events are already encoding-aware; do not decode again here.
|
|
808
|
+
if (resolveNext) {
|
|
809
|
+
resolveNext(chunk);
|
|
810
|
+
resolveNext = null;
|
|
811
|
+
rejectNext = null;
|
|
812
|
+
}
|
|
813
|
+
else {
|
|
814
|
+
dataQueue.push(chunk);
|
|
815
|
+
}
|
|
816
|
+
queuedSize += chunkSizeForBackpressure(chunk);
|
|
817
|
+
if (!pausedByIterator && queuedSize >= highWaterMark) {
|
|
818
|
+
pausedByIterator = true;
|
|
819
|
+
this.pause();
|
|
820
|
+
}
|
|
821
|
+
};
|
|
822
|
+
const endHandler = () => {
|
|
823
|
+
done = true;
|
|
824
|
+
if (resolveNext) {
|
|
825
|
+
resolveNext(null);
|
|
826
|
+
resolveNext = null;
|
|
827
|
+
rejectNext = null;
|
|
828
|
+
}
|
|
829
|
+
};
|
|
830
|
+
const closeHandler = () => {
|
|
831
|
+
done = true;
|
|
832
|
+
if (resolveNext) {
|
|
833
|
+
resolveNext(null);
|
|
834
|
+
resolveNext = null;
|
|
835
|
+
rejectNext = null;
|
|
836
|
+
}
|
|
837
|
+
};
|
|
838
|
+
const errorHandler = (err) => {
|
|
839
|
+
done = true;
|
|
840
|
+
streamError = err;
|
|
841
|
+
if (rejectNext) {
|
|
842
|
+
rejectNext(err);
|
|
843
|
+
resolveNext = null;
|
|
844
|
+
rejectNext = null;
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
this.on("data", dataHandler);
|
|
848
|
+
this.on("end", endHandler);
|
|
849
|
+
this.on("error", errorHandler);
|
|
850
|
+
this.on("close", closeHandler);
|
|
758
851
|
try {
|
|
852
|
+
// Iterator consumption should drive the stream.
|
|
853
|
+
this.resume();
|
|
759
854
|
while (true) {
|
|
760
|
-
|
|
855
|
+
if (streamError) {
|
|
856
|
+
throw streamError;
|
|
857
|
+
}
|
|
858
|
+
if (dataQueueIndex < dataQueue.length) {
|
|
859
|
+
const chunk = dataQueue[dataQueueIndex++];
|
|
860
|
+
queuedSize -= chunkSizeForBackpressure(chunk);
|
|
861
|
+
if (dataQueueIndex >= 1024 && dataQueueIndex * 2 >= dataQueue.length) {
|
|
862
|
+
dataQueue.splice(0, dataQueueIndex);
|
|
863
|
+
dataQueueIndex = 0;
|
|
864
|
+
}
|
|
865
|
+
if (pausedByIterator && queuedSize <= lowWaterMark && !done && !this._destroyed) {
|
|
866
|
+
pausedByIterator = false;
|
|
867
|
+
this.resume();
|
|
868
|
+
}
|
|
869
|
+
yield chunk;
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
761
872
|
if (done) {
|
|
762
873
|
break;
|
|
763
874
|
}
|
|
764
|
-
|
|
875
|
+
const chunk = await new Promise((resolve, reject) => {
|
|
876
|
+
resolveNext = resolve;
|
|
877
|
+
rejectNext = reject;
|
|
878
|
+
});
|
|
879
|
+
if (chunk !== null) {
|
|
880
|
+
queuedSize -= chunkSizeForBackpressure(chunk);
|
|
881
|
+
if (pausedByIterator && queuedSize <= lowWaterMark && !done && !this._destroyed) {
|
|
882
|
+
pausedByIterator = false;
|
|
883
|
+
this.resume();
|
|
884
|
+
}
|
|
885
|
+
yield chunk;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
if (streamError) {
|
|
889
|
+
throw streamError;
|
|
765
890
|
}
|
|
766
891
|
}
|
|
767
892
|
finally {
|
|
768
|
-
this.
|
|
893
|
+
this.off("data", dataHandler);
|
|
894
|
+
this.off("end", endHandler);
|
|
895
|
+
this.off("error", errorHandler);
|
|
896
|
+
this.off("close", closeHandler);
|
|
769
897
|
}
|
|
770
898
|
}
|
|
771
899
|
/**
|
|
@@ -985,6 +1113,32 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
985
1113
|
}
|
|
986
1114
|
}
|
|
987
1115
|
exports.Readable = Readable;
|
|
1116
|
+
function toAsyncIterable(iterable) {
|
|
1117
|
+
if (iterable && typeof iterable[Symbol.asyncIterator] === "function") {
|
|
1118
|
+
return iterable;
|
|
1119
|
+
}
|
|
1120
|
+
return (async function* () {
|
|
1121
|
+
for (const item of iterable) {
|
|
1122
|
+
yield item;
|
|
1123
|
+
}
|
|
1124
|
+
})();
|
|
1125
|
+
}
|
|
1126
|
+
function pumpAsyncIterableToReadable(readable, iterable) {
|
|
1127
|
+
(async () => {
|
|
1128
|
+
try {
|
|
1129
|
+
for await (const chunk of iterable) {
|
|
1130
|
+
if (!readable.push(chunk)) {
|
|
1131
|
+
// Simple backpressure: yield to consumer.
|
|
1132
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
readable.push(null);
|
|
1136
|
+
}
|
|
1137
|
+
catch (err) {
|
|
1138
|
+
readable.destroy(err);
|
|
1139
|
+
}
|
|
1140
|
+
})();
|
|
1141
|
+
}
|
|
988
1142
|
// =============================================================================
|
|
989
1143
|
// Writable Stream Wrapper
|
|
990
1144
|
// =============================================================================
|
|
@@ -1002,10 +1156,12 @@ class Writable extends event_emitter_1.EventEmitter {
|
|
|
1002
1156
|
this._closed = false;
|
|
1003
1157
|
this._pendingWrites = 0;
|
|
1004
1158
|
this._writableLength = 0;
|
|
1159
|
+
this._needDrain = false;
|
|
1005
1160
|
this._corked = 0;
|
|
1006
1161
|
this._corkedChunks = [];
|
|
1007
1162
|
this._defaultEncoding = "utf8";
|
|
1008
1163
|
this._aborted = false;
|
|
1164
|
+
this._ownsStream = false;
|
|
1009
1165
|
this.objectMode = options?.objectMode ?? false;
|
|
1010
1166
|
this.writableHighWaterMark = options?.highWaterMark ?? 16384;
|
|
1011
1167
|
this.autoDestroy = options?.autoDestroy ?? true;
|
|
@@ -1021,8 +1177,10 @@ class Writable extends event_emitter_1.EventEmitter {
|
|
|
1021
1177
|
}
|
|
1022
1178
|
if (options?.stream) {
|
|
1023
1179
|
this._stream = options.stream;
|
|
1180
|
+
this._ownsStream = false;
|
|
1024
1181
|
}
|
|
1025
1182
|
else {
|
|
1183
|
+
this._ownsStream = true;
|
|
1026
1184
|
// Create bound references to instance properties/methods for use in WritableStream callbacks
|
|
1027
1185
|
const getWriteFunc = () => this._writeFunc;
|
|
1028
1186
|
const getFinalFunc = () => this._finalFunc;
|
|
@@ -1129,25 +1287,37 @@ class Writable extends event_emitter_1.EventEmitter {
|
|
|
1129
1287
|
this._writableLength += chunkSize;
|
|
1130
1288
|
return this._writableLength < this.writableHighWaterMark;
|
|
1131
1289
|
}
|
|
1132
|
-
|
|
1290
|
+
const ok = this._doWrite(chunk, cb);
|
|
1291
|
+
if (!ok) {
|
|
1292
|
+
this._needDrain = true;
|
|
1293
|
+
}
|
|
1294
|
+
return ok;
|
|
1133
1295
|
}
|
|
1134
1296
|
_doWrite(chunk, callback) {
|
|
1135
1297
|
// Track pending writes for writableLength
|
|
1136
1298
|
const chunkSize = this._getChunkSize(chunk);
|
|
1137
1299
|
this._pendingWrites++;
|
|
1138
1300
|
this._writableLength += chunkSize;
|
|
1139
|
-
this._getWriter()
|
|
1301
|
+
const writer = this._getWriter();
|
|
1302
|
+
writer
|
|
1140
1303
|
.write(chunk)
|
|
1141
1304
|
.then(() => {
|
|
1142
1305
|
this._pendingWrites--;
|
|
1143
1306
|
this._writableLength -= chunkSize;
|
|
1144
|
-
this.
|
|
1307
|
+
if (this._needDrain && this._writableLength < this.writableHighWaterMark) {
|
|
1308
|
+
this._needDrain = false;
|
|
1309
|
+
this.emit("drain");
|
|
1310
|
+
}
|
|
1145
1311
|
callback?.(null);
|
|
1146
1312
|
})
|
|
1147
1313
|
.catch(err => {
|
|
1148
1314
|
this._pendingWrites--;
|
|
1149
1315
|
this._writableLength -= chunkSize;
|
|
1150
|
-
|
|
1316
|
+
// Avoid double-emitting if we're already in an errored/destroyed state.
|
|
1317
|
+
if (!this._destroyed) {
|
|
1318
|
+
this._errored = err;
|
|
1319
|
+
this.emit("error", err);
|
|
1320
|
+
}
|
|
1151
1321
|
callback?.(err);
|
|
1152
1322
|
});
|
|
1153
1323
|
// Return false if we've exceeded high water mark (for backpressure)
|
|
@@ -1178,12 +1348,29 @@ class Writable extends event_emitter_1.EventEmitter {
|
|
|
1178
1348
|
: callback;
|
|
1179
1349
|
const finish = async () => {
|
|
1180
1350
|
try {
|
|
1351
|
+
const writer = this._getWriter();
|
|
1181
1352
|
if (chunk !== undefined) {
|
|
1182
|
-
await
|
|
1353
|
+
await writer.write(chunk);
|
|
1354
|
+
}
|
|
1355
|
+
await writer.close();
|
|
1356
|
+
if (this._writer === writer) {
|
|
1357
|
+
this._writer = null;
|
|
1358
|
+
try {
|
|
1359
|
+
writer.releaseLock();
|
|
1360
|
+
}
|
|
1361
|
+
catch {
|
|
1362
|
+
// Ignore
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
// If we own the underlying Web WritableStream, its `close()` handler already
|
|
1366
|
+
// emits finish/close. For external streams, we must emit finish ourselves.
|
|
1367
|
+
if (!this._ownsStream) {
|
|
1368
|
+
this._finished = true;
|
|
1369
|
+
this.emit("finish");
|
|
1370
|
+
if (this.emitClose) {
|
|
1371
|
+
this.emit("close");
|
|
1372
|
+
}
|
|
1183
1373
|
}
|
|
1184
|
-
await this._getWriter().close();
|
|
1185
|
-
this._finished = true;
|
|
1186
|
-
this.emit("finish");
|
|
1187
1374
|
if (cb) {
|
|
1188
1375
|
cb();
|
|
1189
1376
|
}
|
|
@@ -1204,12 +1391,24 @@ class Writable extends event_emitter_1.EventEmitter {
|
|
|
1204
1391
|
}
|
|
1205
1392
|
this._destroyed = true;
|
|
1206
1393
|
this._ended = true;
|
|
1207
|
-
if (error) {
|
|
1394
|
+
if (error && !this._errored) {
|
|
1208
1395
|
this._errored = error;
|
|
1209
1396
|
this.emit("error", error);
|
|
1210
1397
|
}
|
|
1211
1398
|
if (this._writer) {
|
|
1212
|
-
this._writer
|
|
1399
|
+
const writer = this._writer;
|
|
1400
|
+
this._writer = null;
|
|
1401
|
+
writer
|
|
1402
|
+
.abort(error)
|
|
1403
|
+
.catch(() => { })
|
|
1404
|
+
.finally(() => {
|
|
1405
|
+
try {
|
|
1406
|
+
writer.releaseLock();
|
|
1407
|
+
}
|
|
1408
|
+
catch {
|
|
1409
|
+
// Ignore
|
|
1410
|
+
}
|
|
1411
|
+
});
|
|
1213
1412
|
}
|
|
1214
1413
|
this._closed = true;
|
|
1215
1414
|
this.emit("close");
|
|
@@ -1361,342 +1560,259 @@ function normalizeWritable(stream) {
|
|
|
1361
1560
|
*/
|
|
1362
1561
|
class Transform extends event_emitter_1.EventEmitter {
|
|
1363
1562
|
/**
|
|
1364
|
-
* Push data to the readable side (Node.js compatibility)
|
|
1365
|
-
*
|
|
1563
|
+
* Push data to the readable side (Node.js compatibility).
|
|
1564
|
+
* Intended to be called from within transform/flush.
|
|
1366
1565
|
*/
|
|
1367
1566
|
push(chunk) {
|
|
1368
|
-
|
|
1369
|
-
return false;
|
|
1370
|
-
}
|
|
1371
|
-
if (this._transformController) {
|
|
1372
|
-
// If we're in a transform callback, enqueue directly
|
|
1373
|
-
this._transformController.enqueue(chunk);
|
|
1374
|
-
}
|
|
1375
|
-
else {
|
|
1376
|
-
// Otherwise buffer for later
|
|
1377
|
-
this._pushBuffer.push(chunk);
|
|
1378
|
-
}
|
|
1379
|
-
return true;
|
|
1567
|
+
return this._readable.push(chunk);
|
|
1380
1568
|
}
|
|
1381
1569
|
constructor(options) {
|
|
1382
1570
|
super();
|
|
1383
|
-
this._ended = false;
|
|
1384
1571
|
this._destroyed = false;
|
|
1572
|
+
this._ended = false;
|
|
1385
1573
|
this._errored = false;
|
|
1386
|
-
// Buffer for Node.js style push() calls during transform
|
|
1387
|
-
this._pushBuffer = [];
|
|
1388
|
-
// Controller for enqueueing pushed data (set during transform execution)
|
|
1389
|
-
this._transformController = null;
|
|
1390
|
-
// Buffer for writes that occur after end() but before writable is closed
|
|
1391
|
-
this._pendingEndWrites = [];
|
|
1392
|
-
// Whether end() has been called but writable not yet closed
|
|
1393
|
-
this._endPending = false;
|
|
1394
|
-
// Track if we've already set up data forwarding
|
|
1395
1574
|
this._dataForwardingSetup = false;
|
|
1396
|
-
|
|
1397
|
-
this.
|
|
1398
|
-
|
|
1399
|
-
this._readableConsuming = false;
|
|
1400
|
-
/** @internal - buffer for auto-consumed data */
|
|
1401
|
-
this._autoConsumedBuffer = [];
|
|
1402
|
-
this._autoConsumedBufferIndex = 0;
|
|
1403
|
-
/** @internal - whether auto-consume has ended */
|
|
1404
|
-
this._autoConsumeEnded = false;
|
|
1405
|
-
/** @internal - promise that resolves when auto-consume finishes */
|
|
1406
|
-
this._autoConsumePromise = null;
|
|
1407
|
-
/** @internal - list of piped destinations for forwarding auto-consumed data */
|
|
1408
|
-
this._pipeDestinations = [];
|
|
1575
|
+
this._endTimer = null;
|
|
1576
|
+
this._webStream = null;
|
|
1577
|
+
this._sideForwardingCleanup = null;
|
|
1409
1578
|
this.objectMode = options?.objectMode ?? false;
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1579
|
+
this._transformImpl = options?.transform;
|
|
1580
|
+
this._flushImpl = options?.flush;
|
|
1581
|
+
this._readable = new Readable({
|
|
1582
|
+
objectMode: this.objectMode
|
|
1583
|
+
});
|
|
1584
|
+
this._writable = new Writable({
|
|
1585
|
+
objectMode: this.objectMode,
|
|
1586
|
+
write: (chunk, _encoding, callback) => {
|
|
1587
|
+
this._runTransform(chunk)
|
|
1588
|
+
.then(() => callback(null))
|
|
1589
|
+
.catch(err => callback(err));
|
|
1590
|
+
},
|
|
1591
|
+
final: callback => {
|
|
1592
|
+
this._runFlush()
|
|
1593
|
+
.then(() => {
|
|
1594
|
+
this._readable.push(null);
|
|
1595
|
+
callback(null);
|
|
1596
|
+
})
|
|
1597
|
+
.catch(err => callback(err));
|
|
1425
1598
|
}
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1599
|
+
});
|
|
1600
|
+
this._setupSideForwarding();
|
|
1601
|
+
}
|
|
1602
|
+
_setupSideForwarding() {
|
|
1603
|
+
if (this._sideForwardingCleanup) {
|
|
1604
|
+
this._sideForwardingCleanup();
|
|
1605
|
+
this._sideForwardingCleanup = null;
|
|
1606
|
+
}
|
|
1607
|
+
const registry = createListenerRegistry();
|
|
1608
|
+
registry.once(this._readable, "end", () => this.emit("end"));
|
|
1609
|
+
registry.add(this._readable, "error", err => this._emitErrorOnce(err));
|
|
1610
|
+
registry.once(this._writable, "finish", () => this.emit("finish"));
|
|
1611
|
+
registry.add(this._writable, "drain", () => this.emit("drain"));
|
|
1612
|
+
registry.add(this._writable, "error", err => this._emitErrorOnce(err));
|
|
1613
|
+
this._sideForwardingCleanup = () => registry.cleanup();
|
|
1614
|
+
}
|
|
1615
|
+
_scheduleEnd() {
|
|
1616
|
+
if (this._destroyed || this._errored) {
|
|
1617
|
+
return;
|
|
1618
|
+
}
|
|
1619
|
+
if (this._writable.writableEnded) {
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1622
|
+
if (this._endTimer) {
|
|
1623
|
+
clearTimeout(this._endTimer);
|
|
1624
|
+
}
|
|
1625
|
+
// Defer closing to allow writes triggered during 'data' callbacks.
|
|
1626
|
+
this._endTimer = setTimeout(() => {
|
|
1627
|
+
this._endTimer = null;
|
|
1628
|
+
if (this._destroyed || this._errored || this._writable.writableEnded) {
|
|
1629
|
+
return;
|
|
1435
1630
|
}
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1631
|
+
this._writable.end();
|
|
1632
|
+
}, 0);
|
|
1633
|
+
}
|
|
1634
|
+
_emitErrorOnce(err) {
|
|
1635
|
+
if (this._errored) {
|
|
1636
|
+
return;
|
|
1637
|
+
}
|
|
1638
|
+
this._errored = true;
|
|
1639
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
1640
|
+
this.emit("error", error);
|
|
1641
|
+
if (!this._destroyed) {
|
|
1642
|
+
this._destroyed = true;
|
|
1643
|
+
this._readable.destroy(error);
|
|
1644
|
+
this._writable.destroy(error);
|
|
1645
|
+
queueMicrotask(() => this.emit("close"));
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
_hasSubclassTransform() {
|
|
1649
|
+
if (this._transformImpl) {
|
|
1650
|
+
return false;
|
|
1651
|
+
}
|
|
1652
|
+
const proto = Object.getPrototypeOf(this);
|
|
1653
|
+
return proto._transform !== Transform.prototype._transform;
|
|
1654
|
+
}
|
|
1655
|
+
_hasSubclassFlush() {
|
|
1656
|
+
if (this._flushImpl) {
|
|
1657
|
+
return false;
|
|
1658
|
+
}
|
|
1659
|
+
const proto = Object.getPrototypeOf(this);
|
|
1660
|
+
return proto._flush !== Transform.prototype._flush;
|
|
1661
|
+
}
|
|
1662
|
+
async _runTransform(chunk) {
|
|
1663
|
+
if (this._destroyed || this._errored) {
|
|
1664
|
+
throw new Error(this._errored ? "Cannot write after stream errored" : "Cannot write after stream destroyed");
|
|
1665
|
+
}
|
|
1666
|
+
try {
|
|
1667
|
+
if (this._hasSubclassTransform()) {
|
|
1668
|
+
await new Promise((resolve, reject) => {
|
|
1669
|
+
this._transform(chunk, "utf8", (err, data) => {
|
|
1670
|
+
if (err) {
|
|
1671
|
+
reject(err);
|
|
1672
|
+
return;
|
|
1673
|
+
}
|
|
1674
|
+
if (data !== undefined) {
|
|
1675
|
+
this.push(data);
|
|
1676
|
+
}
|
|
1677
|
+
resolve();
|
|
1678
|
+
});
|
|
1679
|
+
});
|
|
1680
|
+
return;
|
|
1443
1681
|
}
|
|
1444
|
-
const
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
// Check for subclass _transform override first
|
|
1457
|
-
if (hasSubclassTransform()) {
|
|
1458
|
-
// Call subclass _transform method (Node.js style)
|
|
1459
|
-
// _transform signature is (chunk, encoding, callback)
|
|
1460
|
-
await new Promise((resolve, reject) => {
|
|
1461
|
-
this._transform(chunk, "utf8", (err, data) => {
|
|
1462
|
-
if (err) {
|
|
1463
|
-
reject(err);
|
|
1464
|
-
}
|
|
1465
|
-
else {
|
|
1466
|
-
if (data !== undefined) {
|
|
1467
|
-
controller.enqueue(data);
|
|
1468
|
-
}
|
|
1469
|
-
resolve();
|
|
1470
|
-
}
|
|
1471
|
-
});
|
|
1472
|
-
});
|
|
1473
|
-
}
|
|
1474
|
-
else if (userTransform) {
|
|
1475
|
-
const transformParamCount = userTransform.length;
|
|
1476
|
-
if (transformParamCount >= 3) {
|
|
1477
|
-
// Node.js style: transform(chunk, encoding, callback)
|
|
1478
|
-
await new Promise((resolve, reject) => {
|
|
1479
|
-
userTransform.call(getInstance(), chunk, "utf8", (err, data) => {
|
|
1480
|
-
if (err) {
|
|
1481
|
-
reject(err);
|
|
1482
|
-
}
|
|
1483
|
-
else {
|
|
1484
|
-
if (data !== undefined) {
|
|
1485
|
-
controller.enqueue(data);
|
|
1486
|
-
}
|
|
1487
|
-
resolve();
|
|
1488
|
-
}
|
|
1489
|
-
});
|
|
1490
|
-
});
|
|
1682
|
+
const userTransform = this._transformImpl;
|
|
1683
|
+
if (!userTransform) {
|
|
1684
|
+
this.push(chunk);
|
|
1685
|
+
return;
|
|
1686
|
+
}
|
|
1687
|
+
const paramCount = userTransform.length;
|
|
1688
|
+
if (paramCount >= 3) {
|
|
1689
|
+
await new Promise((resolve, reject) => {
|
|
1690
|
+
userTransform.call(this, chunk, "utf8", (err, data) => {
|
|
1691
|
+
if (err) {
|
|
1692
|
+
reject(err);
|
|
1693
|
+
return;
|
|
1491
1694
|
}
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
userTransform.call(getInstance(), chunk, (err, data) => {
|
|
1495
|
-
if (err) {
|
|
1496
|
-
reject(err);
|
|
1497
|
-
}
|
|
1498
|
-
else {
|
|
1499
|
-
if (data !== undefined) {
|
|
1500
|
-
controller.enqueue(data);
|
|
1501
|
-
}
|
|
1502
|
-
resolve();
|
|
1503
|
-
}
|
|
1504
|
-
});
|
|
1505
|
-
});
|
|
1695
|
+
if (data !== undefined) {
|
|
1696
|
+
this.push(data);
|
|
1506
1697
|
}
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
controller.enqueue(result);
|
|
1519
|
-
}
|
|
1520
|
-
}
|
|
1698
|
+
resolve();
|
|
1699
|
+
});
|
|
1700
|
+
});
|
|
1701
|
+
return;
|
|
1702
|
+
}
|
|
1703
|
+
if (paramCount === 2) {
|
|
1704
|
+
await new Promise((resolve, reject) => {
|
|
1705
|
+
userTransform.call(this, chunk, (err, data) => {
|
|
1706
|
+
if (err) {
|
|
1707
|
+
reject(err);
|
|
1708
|
+
return;
|
|
1521
1709
|
}
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
}
|
|
1527
|
-
}
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1710
|
+
if (data !== undefined) {
|
|
1711
|
+
this.push(data);
|
|
1712
|
+
}
|
|
1713
|
+
resolve();
|
|
1714
|
+
});
|
|
1715
|
+
});
|
|
1716
|
+
return;
|
|
1717
|
+
}
|
|
1718
|
+
const result = userTransform.call(this, chunk);
|
|
1719
|
+
if (result && typeof result.then === "function") {
|
|
1720
|
+
const awaited = await result;
|
|
1721
|
+
if (awaited !== undefined) {
|
|
1722
|
+
this.push(awaited);
|
|
1534
1723
|
}
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
if (flushParamCount >= 1) {
|
|
1558
|
-
// Node.js style: flush(callback)
|
|
1559
|
-
await new Promise((resolve, reject) => {
|
|
1560
|
-
userFlush.call(getInstance(), (err, data) => {
|
|
1561
|
-
if (err) {
|
|
1562
|
-
reject(err);
|
|
1563
|
-
}
|
|
1564
|
-
else {
|
|
1565
|
-
if (data !== undefined) {
|
|
1566
|
-
controller.enqueue(data);
|
|
1567
|
-
}
|
|
1568
|
-
resolve();
|
|
1569
|
-
}
|
|
1570
|
-
});
|
|
1571
|
-
});
|
|
1724
|
+
return;
|
|
1725
|
+
}
|
|
1726
|
+
if (result !== undefined) {
|
|
1727
|
+
this.push(result);
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
catch (err) {
|
|
1731
|
+
this._emitErrorOnce(err);
|
|
1732
|
+
throw err;
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
async _runFlush() {
|
|
1736
|
+
if (this._destroyed || this._errored) {
|
|
1737
|
+
return;
|
|
1738
|
+
}
|
|
1739
|
+
try {
|
|
1740
|
+
if (this._hasSubclassFlush()) {
|
|
1741
|
+
await new Promise((resolve, reject) => {
|
|
1742
|
+
this._flush((err, data) => {
|
|
1743
|
+
if (err) {
|
|
1744
|
+
reject(err);
|
|
1745
|
+
return;
|
|
1572
1746
|
}
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
const result = userFlush.call(getInstance());
|
|
1576
|
-
if (result && typeof result.then === "function") {
|
|
1577
|
-
const awaitedResult = await result;
|
|
1578
|
-
if (awaitedResult !== undefined && awaitedResult !== null) {
|
|
1579
|
-
controller.enqueue(awaitedResult);
|
|
1580
|
-
}
|
|
1581
|
-
}
|
|
1582
|
-
else {
|
|
1583
|
-
if (result !== undefined && result !== null) {
|
|
1584
|
-
controller.enqueue(result);
|
|
1585
|
-
}
|
|
1586
|
-
}
|
|
1747
|
+
if (data !== undefined) {
|
|
1748
|
+
this.push(data);
|
|
1587
1749
|
}
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
}
|
|
1591
|
-
|
|
1592
|
-
controller.error(err);
|
|
1593
|
-
emitEvent("error", err);
|
|
1594
|
-
}
|
|
1595
|
-
finally {
|
|
1596
|
-
setController(null);
|
|
1597
|
-
}
|
|
1750
|
+
resolve();
|
|
1751
|
+
});
|
|
1752
|
+
});
|
|
1753
|
+
return;
|
|
1598
1754
|
}
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
objectMode: this.objectMode
|
|
1603
|
-
});
|
|
1604
|
-
this._writable = new Writable({
|
|
1605
|
-
stream: this._stream.writable,
|
|
1606
|
-
objectMode: this.objectMode
|
|
1607
|
-
});
|
|
1608
|
-
// Forward non-data events (data forwarding is lazy to avoid premature flowing)
|
|
1609
|
-
this._readable.on("end", () => this.emit("end"));
|
|
1610
|
-
// Only forward errors if not already errored (to prevent duplicate events)
|
|
1611
|
-
this._readable.on("error", err => {
|
|
1612
|
-
if (!this._errored) {
|
|
1613
|
-
this._errored = true;
|
|
1614
|
-
this.emit("error", err);
|
|
1755
|
+
const userFlush = this._flushImpl;
|
|
1756
|
+
if (!userFlush) {
|
|
1757
|
+
return;
|
|
1615
1758
|
}
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1759
|
+
const paramCount = userFlush.length;
|
|
1760
|
+
if (paramCount >= 1) {
|
|
1761
|
+
await new Promise((resolve, reject) => {
|
|
1762
|
+
userFlush.call(this, (err, data) => {
|
|
1763
|
+
if (err) {
|
|
1764
|
+
reject(err);
|
|
1765
|
+
return;
|
|
1766
|
+
}
|
|
1767
|
+
if (data !== undefined) {
|
|
1768
|
+
this.push(data);
|
|
1769
|
+
}
|
|
1770
|
+
resolve();
|
|
1771
|
+
});
|
|
1772
|
+
});
|
|
1773
|
+
return;
|
|
1624
1774
|
}
|
|
1625
|
-
|
|
1775
|
+
const result = userFlush.call(this);
|
|
1776
|
+
if (result && typeof result.then === "function") {
|
|
1777
|
+
const awaited = await result;
|
|
1778
|
+
if (awaited !== undefined && awaited !== null) {
|
|
1779
|
+
this.push(awaited);
|
|
1780
|
+
}
|
|
1781
|
+
return;
|
|
1782
|
+
}
|
|
1783
|
+
if (result !== undefined && result !== null) {
|
|
1784
|
+
this.push(result);
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
catch (err) {
|
|
1788
|
+
this._emitErrorOnce(err);
|
|
1789
|
+
throw err;
|
|
1790
|
+
}
|
|
1626
1791
|
}
|
|
1627
1792
|
/**
|
|
1628
|
-
* Override on to
|
|
1793
|
+
* Override on() to lazily forward readable 'data' events.
|
|
1794
|
+
* Avoids starting flowing mode unless requested.
|
|
1629
1795
|
*/
|
|
1630
1796
|
on(event, listener) {
|
|
1631
|
-
// Set up data forwarding when first external data listener is added
|
|
1632
1797
|
if (event === "data" && !this._dataForwardingSetup) {
|
|
1633
1798
|
this._dataForwardingSetup = true;
|
|
1634
|
-
this._readable.on("data",
|
|
1635
|
-
}
|
|
1636
|
-
super.on(event, listener);
|
|
1637
|
-
// When data listener is added, mark as having consumer
|
|
1638
|
-
// and start the readable in flowing mode
|
|
1639
|
-
if (event === "data") {
|
|
1640
|
-
this._hasDataConsumer = true;
|
|
1641
|
-
this._readable.resume();
|
|
1799
|
+
this._readable.on("data", chunk => this.emit("data", chunk));
|
|
1642
1800
|
}
|
|
1643
|
-
return
|
|
1801
|
+
return super.on(event, listener);
|
|
1644
1802
|
}
|
|
1645
1803
|
write(chunk, encodingOrCallback, callback) {
|
|
1646
1804
|
const cb = typeof encodingOrCallback === "function" ? encodingOrCallback : callback;
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
cb?.(err);
|
|
1651
|
-
return false;
|
|
1652
|
-
}
|
|
1653
|
-
// Ensure readable is being consumed to allow transform to execute
|
|
1654
|
-
// This matches Node.js behavior where transform executes immediately on write
|
|
1655
|
-
// Only auto-consume if no explicit consumer (data listener or pipe)
|
|
1656
|
-
if (!this._readableConsuming && !this._hasDataConsumer) {
|
|
1657
|
-
this._readableConsuming = true;
|
|
1658
|
-
this._startAutoConsume();
|
|
1659
|
-
}
|
|
1660
|
-
// If end() was called but writable not yet closed, buffer the write
|
|
1661
|
-
// This allows writes during data event handlers to be processed
|
|
1662
|
-
if (this._endPending) {
|
|
1663
|
-
this._pendingEndWrites.push({ chunk, callback: cb });
|
|
1664
|
-
return true;
|
|
1805
|
+
// If end() has been requested, keep the close deferred as long as writes continue.
|
|
1806
|
+
if (this._ended && !this._writable.writableEnded) {
|
|
1807
|
+
this._scheduleEnd();
|
|
1665
1808
|
}
|
|
1666
1809
|
return this._writable.write(chunk, cb);
|
|
1667
1810
|
}
|
|
1668
|
-
/** @internal - auto-consume readable to allow transform to execute */
|
|
1669
|
-
_startAutoConsume() {
|
|
1670
|
-
this._autoConsumePromise = (async () => {
|
|
1671
|
-
try {
|
|
1672
|
-
for await (const chunk of this._readable) {
|
|
1673
|
-
// Buffer the data for later retrieval
|
|
1674
|
-
this._autoConsumedBuffer.push(chunk);
|
|
1675
|
-
// Forward to any piped destinations
|
|
1676
|
-
for (const dest of this._pipeDestinations) {
|
|
1677
|
-
dest.write(chunk);
|
|
1678
|
-
}
|
|
1679
|
-
// Also emit data event for listeners
|
|
1680
|
-
this.emit("data", chunk);
|
|
1681
|
-
}
|
|
1682
|
-
this._autoConsumeEnded = true;
|
|
1683
|
-
// End all piped destinations
|
|
1684
|
-
for (const dest of this._pipeDestinations) {
|
|
1685
|
-
dest.end();
|
|
1686
|
-
}
|
|
1687
|
-
this.emit("end");
|
|
1688
|
-
}
|
|
1689
|
-
catch (err) {
|
|
1690
|
-
this.emit("error", err);
|
|
1691
|
-
}
|
|
1692
|
-
})();
|
|
1693
|
-
}
|
|
1694
1811
|
end(chunkOrCallback, encodingOrCallback, callback) {
|
|
1695
1812
|
if (this._ended) {
|
|
1696
1813
|
return this;
|
|
1697
1814
|
}
|
|
1698
1815
|
this._ended = true;
|
|
1699
|
-
this._endPending = true;
|
|
1700
1816
|
const chunk = typeof chunkOrCallback === "function" ? undefined : chunkOrCallback;
|
|
1701
1817
|
const cb = typeof chunkOrCallback === "function"
|
|
1702
1818
|
? chunkOrCallback
|
|
@@ -1709,18 +1825,7 @@ class Transform extends event_emitter_1.EventEmitter {
|
|
|
1709
1825
|
if (chunk !== undefined) {
|
|
1710
1826
|
this._writable.write(chunk);
|
|
1711
1827
|
}
|
|
1712
|
-
|
|
1713
|
-
// processing and data events complete before we close the writable.
|
|
1714
|
-
// Microtasks run before the TransformStream processes data.
|
|
1715
|
-
setTimeout(() => {
|
|
1716
|
-
// Process any writes that occurred during data events
|
|
1717
|
-
for (const { chunk: pendingChunk, callback } of this._pendingEndWrites) {
|
|
1718
|
-
this._writable.write(pendingChunk, callback);
|
|
1719
|
-
}
|
|
1720
|
-
this._pendingEndWrites = [];
|
|
1721
|
-
this._endPending = false;
|
|
1722
|
-
this._writable.end();
|
|
1723
|
-
}, 0);
|
|
1828
|
+
this._scheduleEnd();
|
|
1724
1829
|
return this;
|
|
1725
1830
|
}
|
|
1726
1831
|
/**
|
|
@@ -1730,31 +1835,9 @@ class Transform extends event_emitter_1.EventEmitter {
|
|
|
1730
1835
|
return this._readable.read(size);
|
|
1731
1836
|
}
|
|
1732
1837
|
/**
|
|
1733
|
-
* Pipe
|
|
1838
|
+
* Pipe readable side to destination
|
|
1734
1839
|
*/
|
|
1735
1840
|
pipe(destination) {
|
|
1736
|
-
// Mark as having consumer to prevent new auto-consume from starting
|
|
1737
|
-
this._hasDataConsumer = true;
|
|
1738
|
-
// Get the writable target - handle both Transform (with internal _writable) and plain Writable
|
|
1739
|
-
const dest = destination;
|
|
1740
|
-
const target = dest?._writable ?? dest;
|
|
1741
|
-
// Register destination for forwarding
|
|
1742
|
-
this._pipeDestinations.push(target);
|
|
1743
|
-
// If auto-consume is running or has run, we need to handle buffered data ourselves
|
|
1744
|
-
if (this._readableConsuming) {
|
|
1745
|
-
// Forward any buffered data from auto-consume to the destination
|
|
1746
|
-
for (let i = 0; i < this._autoConsumedBuffer.length; i++) {
|
|
1747
|
-
target.write(this._autoConsumedBuffer[i]);
|
|
1748
|
-
}
|
|
1749
|
-
// If auto-consume has ended, end the destination too
|
|
1750
|
-
if (this._autoConsumeEnded) {
|
|
1751
|
-
target.end();
|
|
1752
|
-
}
|
|
1753
|
-
// Don't call _readable.pipe() - auto-consume already consumed _readable
|
|
1754
|
-
// Future data will be forwarded via the 'data' event listener below
|
|
1755
|
-
return destination;
|
|
1756
|
-
}
|
|
1757
|
-
// No auto-consume running - use normal pipe through _readable
|
|
1758
1841
|
return this._readable.pipe(destination);
|
|
1759
1842
|
}
|
|
1760
1843
|
/**
|
|
@@ -1792,6 +1875,10 @@ class Transform extends event_emitter_1.EventEmitter {
|
|
|
1792
1875
|
return;
|
|
1793
1876
|
}
|
|
1794
1877
|
this._destroyed = true;
|
|
1878
|
+
if (this._sideForwardingCleanup) {
|
|
1879
|
+
this._sideForwardingCleanup();
|
|
1880
|
+
this._sideForwardingCleanup = null;
|
|
1881
|
+
}
|
|
1795
1882
|
this._readable.destroy(error);
|
|
1796
1883
|
this._writable.destroy(error);
|
|
1797
1884
|
queueMicrotask(() => this.emit("close"));
|
|
@@ -1800,7 +1887,44 @@ class Transform extends event_emitter_1.EventEmitter {
|
|
|
1800
1887
|
* Get the underlying Web TransformStream
|
|
1801
1888
|
*/
|
|
1802
1889
|
get webStream() {
|
|
1803
|
-
|
|
1890
|
+
if (this._webStream) {
|
|
1891
|
+
return this._webStream;
|
|
1892
|
+
}
|
|
1893
|
+
// Web Streams interop layer.
|
|
1894
|
+
const iterator = this[Symbol.asyncIterator]();
|
|
1895
|
+
const readable = new ReadableStream({
|
|
1896
|
+
pull: async (controller) => {
|
|
1897
|
+
const { done, value } = await iterator.next();
|
|
1898
|
+
if (done) {
|
|
1899
|
+
controller.close();
|
|
1900
|
+
return;
|
|
1901
|
+
}
|
|
1902
|
+
controller.enqueue(value);
|
|
1903
|
+
},
|
|
1904
|
+
cancel: reason => {
|
|
1905
|
+
this.destroy(reason instanceof Error ? reason : new Error(String(reason)));
|
|
1906
|
+
}
|
|
1907
|
+
});
|
|
1908
|
+
const writable = new WritableStream({
|
|
1909
|
+
write: chunk => new Promise((resolve, reject) => {
|
|
1910
|
+
this.write(chunk, err => {
|
|
1911
|
+
if (err) {
|
|
1912
|
+
reject(err);
|
|
1913
|
+
}
|
|
1914
|
+
else {
|
|
1915
|
+
resolve();
|
|
1916
|
+
}
|
|
1917
|
+
});
|
|
1918
|
+
}),
|
|
1919
|
+
close: () => new Promise(resolve => {
|
|
1920
|
+
this.end(() => resolve());
|
|
1921
|
+
}),
|
|
1922
|
+
abort: reason => {
|
|
1923
|
+
this.destroy(reason instanceof Error ? reason : new Error(String(reason)));
|
|
1924
|
+
}
|
|
1925
|
+
});
|
|
1926
|
+
this._webStream = { readable, writable };
|
|
1927
|
+
return this._webStream;
|
|
1804
1928
|
}
|
|
1805
1929
|
get readable() {
|
|
1806
1930
|
return this._readable.readable;
|
|
@@ -1842,19 +1966,6 @@ class Transform extends event_emitter_1.EventEmitter {
|
|
|
1842
1966
|
* Async iterator support
|
|
1843
1967
|
*/
|
|
1844
1968
|
async *[Symbol.asyncIterator]() {
|
|
1845
|
-
// If auto-consume is running, wait for it to finish and use its buffer
|
|
1846
|
-
if (this._autoConsumePromise) {
|
|
1847
|
-
await this._autoConsumePromise;
|
|
1848
|
-
// Yield all buffered data
|
|
1849
|
-
while (this._autoConsumedBufferIndex < this._autoConsumedBuffer.length) {
|
|
1850
|
-
yield this._autoConsumedBuffer[this._autoConsumedBufferIndex++];
|
|
1851
|
-
}
|
|
1852
|
-
// Reset when drained to avoid prefix growth
|
|
1853
|
-
this._autoConsumedBuffer.length = 0;
|
|
1854
|
-
this._autoConsumedBufferIndex = 0;
|
|
1855
|
-
return;
|
|
1856
|
-
}
|
|
1857
|
-
// Otherwise delegate to readable's iterator
|
|
1858
1969
|
yield* this._readable[Symbol.asyncIterator]();
|
|
1859
1970
|
}
|
|
1860
1971
|
// =========================================================================
|
|
@@ -1865,23 +1976,18 @@ class Transform extends event_emitter_1.EventEmitter {
|
|
|
1865
1976
|
*/
|
|
1866
1977
|
static fromWeb(webStream, options) {
|
|
1867
1978
|
const transform = new Transform(options);
|
|
1868
|
-
|
|
1869
|
-
transform._stream = webStream;
|
|
1979
|
+
transform._webStream = webStream;
|
|
1870
1980
|
// Replace internal streams with the ones from the web stream
|
|
1871
1981
|
const newReadable = Readable.fromWeb(webStream.readable, { objectMode: options?.objectMode });
|
|
1872
1982
|
const newWritable = Writable.fromWeb(webStream.writable, { objectMode: options?.objectMode });
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1983
|
+
if (transform._sideForwardingCleanup) {
|
|
1984
|
+
transform._sideForwardingCleanup();
|
|
1985
|
+
transform._sideForwardingCleanup = null;
|
|
1986
|
+
}
|
|
1876
1987
|
transform._readable = newReadable;
|
|
1877
1988
|
transform._writable = newWritable;
|
|
1878
|
-
// Re-connect event forwarding
|
|
1879
|
-
|
|
1880
|
-
newReadable.on("end", () => transform.emit("end"));
|
|
1881
|
-
newReadable.on("error", (err) => transform.emit("error", err));
|
|
1882
|
-
newWritable.on("finish", () => transform.emit("finish"));
|
|
1883
|
-
newWritable.on("drain", () => transform.emit("drain"));
|
|
1884
|
-
newWritable.on("error", (err) => transform.emit("error", err));
|
|
1989
|
+
// Re-connect event forwarding (data forwarding remains lazy via Transform.on)
|
|
1990
|
+
transform._setupSideForwarding();
|
|
1885
1991
|
return transform;
|
|
1886
1992
|
}
|
|
1887
1993
|
/**
|
|
@@ -1938,7 +2044,13 @@ class Duplex extends event_emitter_1.EventEmitter {
|
|
|
1938
2044
|
callback();
|
|
1939
2045
|
}
|
|
1940
2046
|
});
|
|
1941
|
-
|
|
2047
|
+
const onError = (err) => {
|
|
2048
|
+
duplex.emit("error", err);
|
|
2049
|
+
};
|
|
2050
|
+
const cleanupError = addEmitterListener(readable, "error", onError);
|
|
2051
|
+
addEmitterListener(readable, "end", cleanupError, { once: true });
|
|
2052
|
+
addEmitterListener(readable, "close", cleanupError, { once: true });
|
|
2053
|
+
addEmitterListener(sink, "finish", cleanupError, { once: true });
|
|
1942
2054
|
readable.pipe(sink);
|
|
1943
2055
|
};
|
|
1944
2056
|
// If it has readable and/or writable properties
|
|
@@ -1946,21 +2058,25 @@ class Duplex extends event_emitter_1.EventEmitter {
|
|
|
1946
2058
|
source !== null &&
|
|
1947
2059
|
"readable" in source &&
|
|
1948
2060
|
"writable" in source) {
|
|
1949
|
-
const duplex = new Duplex();
|
|
1950
2061
|
const pair = source;
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
2062
|
+
// Create one duplex that can bridge both sides.
|
|
2063
|
+
// (Previous behavior returned a new writable-only Duplex and dropped the readable side.)
|
|
2064
|
+
const duplex = new Duplex({
|
|
2065
|
+
readableObjectMode: pair.readable?.readableObjectMode,
|
|
2066
|
+
writableObjectMode: pair.writable?.writableObjectMode,
|
|
2067
|
+
write: pair.writable
|
|
2068
|
+
? (chunk, encoding, callback) => {
|
|
1958
2069
|
pair.writable.write(chunk, encoding, callback);
|
|
1959
|
-
}
|
|
1960
|
-
|
|
2070
|
+
}
|
|
2071
|
+
: undefined,
|
|
2072
|
+
final: pair.writable
|
|
2073
|
+
? callback => {
|
|
1961
2074
|
pair.writable.end(callback);
|
|
1962
2075
|
}
|
|
1963
|
-
|
|
2076
|
+
: undefined
|
|
2077
|
+
});
|
|
2078
|
+
if (pair.readable) {
|
|
2079
|
+
forwardReadableToDuplex(pair.readable, duplex);
|
|
1964
2080
|
}
|
|
1965
2081
|
return duplex;
|
|
1966
2082
|
}
|
|
@@ -1998,9 +2114,22 @@ class Duplex extends event_emitter_1.EventEmitter {
|
|
|
1998
2114
|
*/
|
|
1999
2115
|
static fromWeb(pair, options) {
|
|
2000
2116
|
const duplex = new Duplex(options);
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2117
|
+
const newReadable = new Readable({
|
|
2118
|
+
stream: pair.readable,
|
|
2119
|
+
objectMode: duplex.readableObjectMode
|
|
2120
|
+
});
|
|
2121
|
+
const newWritable = new Writable({
|
|
2122
|
+
stream: pair.writable,
|
|
2123
|
+
objectMode: duplex.writableObjectMode
|
|
2124
|
+
});
|
|
2125
|
+
if (duplex._sideForwardingCleanup) {
|
|
2126
|
+
duplex._sideForwardingCleanup();
|
|
2127
|
+
duplex._sideForwardingCleanup = null;
|
|
2128
|
+
}
|
|
2129
|
+
duplex._readable = newReadable;
|
|
2130
|
+
duplex._writable = newWritable;
|
|
2131
|
+
// Re-wire event forwarding (data forwarding remains lazy via Duplex.on)
|
|
2132
|
+
duplex._setupSideForwarding();
|
|
2004
2133
|
return duplex;
|
|
2005
2134
|
}
|
|
2006
2135
|
/**
|
|
@@ -2016,6 +2145,7 @@ class Duplex extends event_emitter_1.EventEmitter {
|
|
|
2016
2145
|
super();
|
|
2017
2146
|
// Track if we've already set up data forwarding
|
|
2018
2147
|
this._dataForwardingSetup = false;
|
|
2148
|
+
this._sideForwardingCleanup = null;
|
|
2019
2149
|
this.allowHalfOpen = options?.allowHalfOpen ?? true;
|
|
2020
2150
|
// Support shorthand objectMode option
|
|
2021
2151
|
const objectMode = options?.objectMode ?? false;
|
|
@@ -2032,23 +2162,31 @@ class Duplex extends event_emitter_1.EventEmitter {
|
|
|
2032
2162
|
write: options?.write?.bind(this),
|
|
2033
2163
|
final: options?.final?.bind(this)
|
|
2034
2164
|
});
|
|
2165
|
+
this._setupSideForwarding();
|
|
2166
|
+
}
|
|
2167
|
+
_setupSideForwarding() {
|
|
2168
|
+
if (this._sideForwardingCleanup) {
|
|
2169
|
+
this._sideForwardingCleanup();
|
|
2170
|
+
this._sideForwardingCleanup = null;
|
|
2171
|
+
}
|
|
2172
|
+
const registry = createListenerRegistry();
|
|
2035
2173
|
// Forward non-data events (data forwarding is lazy to avoid premature flowing)
|
|
2036
|
-
this._readable
|
|
2174
|
+
registry.once(this._readable, "end", () => {
|
|
2037
2175
|
this.emit("end");
|
|
2038
|
-
// If not allowHalfOpen, end the writable side too
|
|
2039
2176
|
if (!this.allowHalfOpen) {
|
|
2040
2177
|
this._writable.end();
|
|
2041
2178
|
}
|
|
2042
2179
|
});
|
|
2043
|
-
this._readable
|
|
2044
|
-
this._writable
|
|
2045
|
-
this._writable
|
|
2046
|
-
this._writable
|
|
2047
|
-
|
|
2180
|
+
registry.add(this._readable, "error", err => this.emit("error", err));
|
|
2181
|
+
registry.add(this._writable, "error", err => this.emit("error", err));
|
|
2182
|
+
registry.once(this._writable, "finish", () => this.emit("finish"));
|
|
2183
|
+
registry.add(this._writable, "drain", () => this.emit("drain"));
|
|
2184
|
+
registry.once(this._writable, "close", () => {
|
|
2048
2185
|
if (!this.allowHalfOpen && !this._readable.destroyed) {
|
|
2049
2186
|
this._readable.destroy();
|
|
2050
2187
|
}
|
|
2051
2188
|
});
|
|
2189
|
+
this._sideForwardingCleanup = () => registry.cleanup();
|
|
2052
2190
|
}
|
|
2053
2191
|
/**
|
|
2054
2192
|
* Override on() to set up data forwarding lazily
|
|
@@ -2167,6 +2305,10 @@ class Duplex extends event_emitter_1.EventEmitter {
|
|
|
2167
2305
|
* Destroy both sides
|
|
2168
2306
|
*/
|
|
2169
2307
|
destroy(error) {
|
|
2308
|
+
if (this._sideForwardingCleanup) {
|
|
2309
|
+
this._sideForwardingCleanup();
|
|
2310
|
+
this._sideForwardingCleanup = null;
|
|
2311
|
+
}
|
|
2170
2312
|
this._readable.destroy(error);
|
|
2171
2313
|
this._writable.destroy(error);
|
|
2172
2314
|
return this;
|
|
@@ -2336,36 +2478,16 @@ exports.BufferedStream = BufferedStream;
|
|
|
2336
2478
|
* Create a readable stream with custom read implementation
|
|
2337
2479
|
*/
|
|
2338
2480
|
function createReadable(options) {
|
|
2339
|
-
|
|
2340
|
-
//
|
|
2341
|
-
|
|
2342
|
-
const originalRead = readable.read.bind(readable);
|
|
2343
|
-
readable.read = function (size) {
|
|
2344
|
-
options.read(size ?? 16384);
|
|
2345
|
-
return originalRead(size);
|
|
2346
|
-
};
|
|
2347
|
-
}
|
|
2348
|
-
return readable;
|
|
2481
|
+
// Readable already supports Node-style `read()` via the constructor option.
|
|
2482
|
+
// Keep this helper minimal to avoid accidental double-read behavior.
|
|
2483
|
+
return new Readable(options);
|
|
2349
2484
|
}
|
|
2350
2485
|
/**
|
|
2351
2486
|
* Create a readable stream from an async iterable
|
|
2352
2487
|
*/
|
|
2353
2488
|
function createReadableFromAsyncIterable(iterable, options) {
|
|
2354
2489
|
const readable = new Readable({ ...options, objectMode: options?.objectMode ?? true });
|
|
2355
|
-
(
|
|
2356
|
-
try {
|
|
2357
|
-
for await (const chunk of iterable) {
|
|
2358
|
-
if (!readable.push(chunk)) {
|
|
2359
|
-
// Backpressure - wait a bit
|
|
2360
|
-
await new Promise(resolve => setTimeout(resolve, 0));
|
|
2361
|
-
}
|
|
2362
|
-
}
|
|
2363
|
-
readable.push(null);
|
|
2364
|
-
}
|
|
2365
|
-
catch (err) {
|
|
2366
|
-
readable.destroy(err);
|
|
2367
|
-
}
|
|
2368
|
-
})();
|
|
2490
|
+
pumpAsyncIterableToReadable(readable, iterable);
|
|
2369
2491
|
return readable;
|
|
2370
2492
|
}
|
|
2371
2493
|
/**
|
|
@@ -2394,38 +2516,8 @@ function createReadableFromArray(data, options) {
|
|
|
2394
2516
|
* Create a writable stream with custom write implementation
|
|
2395
2517
|
*/
|
|
2396
2518
|
function createWritable(options) {
|
|
2397
|
-
//
|
|
2398
|
-
|
|
2399
|
-
write: async (chunk) => {
|
|
2400
|
-
if (options?.write) {
|
|
2401
|
-
return new Promise((resolve, reject) => {
|
|
2402
|
-
options.write(chunk, "utf8", err => {
|
|
2403
|
-
if (err) {
|
|
2404
|
-
reject(err);
|
|
2405
|
-
}
|
|
2406
|
-
else {
|
|
2407
|
-
resolve();
|
|
2408
|
-
}
|
|
2409
|
-
});
|
|
2410
|
-
});
|
|
2411
|
-
}
|
|
2412
|
-
},
|
|
2413
|
-
close: async () => {
|
|
2414
|
-
if (options?.final) {
|
|
2415
|
-
return new Promise((resolve, reject) => {
|
|
2416
|
-
options.final(err => {
|
|
2417
|
-
if (err) {
|
|
2418
|
-
reject(err);
|
|
2419
|
-
}
|
|
2420
|
-
else {
|
|
2421
|
-
resolve();
|
|
2422
|
-
}
|
|
2423
|
-
});
|
|
2424
|
-
});
|
|
2425
|
-
}
|
|
2426
|
-
}
|
|
2427
|
-
});
|
|
2428
|
-
return new Writable({ ...options, stream });
|
|
2519
|
+
// Writable already supports Node-style `write()` / `final()` via the constructor.
|
|
2520
|
+
return new Writable(options);
|
|
2429
2521
|
}
|
|
2430
2522
|
/**
|
|
2431
2523
|
* Create a transform stream from a transform function
|
|
@@ -2560,11 +2652,17 @@ function pipeline(...args) {
|
|
|
2560
2652
|
const transforms = normalized.slice(1, -1);
|
|
2561
2653
|
let completed = false;
|
|
2562
2654
|
const allStreams = [source, ...transforms, destination];
|
|
2563
|
-
const
|
|
2655
|
+
const registry = createListenerRegistry();
|
|
2656
|
+
let onAbort;
|
|
2657
|
+
const cleanupWithSignal = (error) => {
|
|
2564
2658
|
if (completed) {
|
|
2565
2659
|
return;
|
|
2566
2660
|
}
|
|
2567
2661
|
completed = true;
|
|
2662
|
+
registry.cleanup();
|
|
2663
|
+
if (onAbort && options.signal) {
|
|
2664
|
+
options.signal.removeEventListener("abort", onAbort);
|
|
2665
|
+
}
|
|
2568
2666
|
// Destroy all streams on error
|
|
2569
2667
|
if (error) {
|
|
2570
2668
|
for (const stream of allStreams) {
|
|
@@ -2581,12 +2679,11 @@ function pipeline(...args) {
|
|
|
2581
2679
|
// Handle abort signal
|
|
2582
2680
|
if (options.signal) {
|
|
2583
2681
|
if (options.signal.aborted) {
|
|
2584
|
-
|
|
2682
|
+
cleanupWithSignal(new Error("Pipeline aborted"));
|
|
2585
2683
|
return;
|
|
2586
2684
|
}
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
});
|
|
2685
|
+
onAbort = () => cleanupWithSignal(new Error("Pipeline aborted"));
|
|
2686
|
+
options.signal.addEventListener("abort", onAbort);
|
|
2590
2687
|
}
|
|
2591
2688
|
// Chain the streams
|
|
2592
2689
|
let current = source;
|
|
@@ -2600,13 +2697,35 @@ function pipeline(...args) {
|
|
|
2600
2697
|
}
|
|
2601
2698
|
else {
|
|
2602
2699
|
// Don't end destination
|
|
2603
|
-
|
|
2700
|
+
let paused = false;
|
|
2701
|
+
let waitingForDrain = false;
|
|
2702
|
+
const onDrain = () => {
|
|
2703
|
+
waitingForDrain = false;
|
|
2704
|
+
if (paused && typeof current.resume === "function") {
|
|
2705
|
+
paused = false;
|
|
2706
|
+
current.resume();
|
|
2707
|
+
}
|
|
2708
|
+
};
|
|
2709
|
+
const onData = (chunk) => {
|
|
2710
|
+
const ok = destination.write(chunk);
|
|
2711
|
+
if (!ok && !waitingForDrain) {
|
|
2712
|
+
waitingForDrain = true;
|
|
2713
|
+
if (!paused && typeof current.pause === "function") {
|
|
2714
|
+
paused = true;
|
|
2715
|
+
current.pause();
|
|
2716
|
+
}
|
|
2717
|
+
registry.once(destination, "drain", onDrain);
|
|
2718
|
+
}
|
|
2719
|
+
};
|
|
2720
|
+
const onEnd = () => cleanupWithSignal();
|
|
2721
|
+
registry.add(current, "data", onData);
|
|
2722
|
+
registry.once(current, "end", onEnd);
|
|
2604
2723
|
}
|
|
2605
2724
|
// Handle completion
|
|
2606
|
-
|
|
2725
|
+
registry.once(destination, "finish", () => cleanupWithSignal());
|
|
2607
2726
|
// Handle errors on all streams
|
|
2608
2727
|
for (const stream of allStreams) {
|
|
2609
|
-
|
|
2728
|
+
registry.once(stream, "error", (err) => cleanupWithSignal(err));
|
|
2610
2729
|
}
|
|
2611
2730
|
});
|
|
2612
2731
|
// If callback provided, use it
|
|
@@ -2646,11 +2765,20 @@ function finished(stream, optionsOrCallback, callback) {
|
|
|
2646
2765
|
const promise = new Promise((resolve, reject) => {
|
|
2647
2766
|
const normalizedStream = toBrowserPipelineStream(stream);
|
|
2648
2767
|
let resolved = false;
|
|
2768
|
+
const registry = createListenerRegistry();
|
|
2769
|
+
let onAbort;
|
|
2770
|
+
const cleanup = () => {
|
|
2771
|
+
registry.cleanup();
|
|
2772
|
+
if (onAbort && options.signal) {
|
|
2773
|
+
options.signal.removeEventListener("abort", onAbort);
|
|
2774
|
+
}
|
|
2775
|
+
};
|
|
2649
2776
|
const done = (err) => {
|
|
2650
2777
|
if (resolved) {
|
|
2651
2778
|
return;
|
|
2652
2779
|
}
|
|
2653
2780
|
resolved = true;
|
|
2781
|
+
cleanup();
|
|
2654
2782
|
if (err && !options.error) {
|
|
2655
2783
|
reject(err);
|
|
2656
2784
|
}
|
|
@@ -2664,9 +2792,8 @@ function finished(stream, optionsOrCallback, callback) {
|
|
|
2664
2792
|
done(new Error("Aborted"));
|
|
2665
2793
|
return;
|
|
2666
2794
|
}
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
});
|
|
2795
|
+
onAbort = () => done(new Error("Aborted"));
|
|
2796
|
+
options.signal.addEventListener("abort", onAbort);
|
|
2670
2797
|
}
|
|
2671
2798
|
const checkReadable = options.readable !== false;
|
|
2672
2799
|
const checkWritable = options.writable !== false;
|
|
@@ -2681,13 +2808,13 @@ function finished(stream, optionsOrCallback, callback) {
|
|
|
2681
2808
|
}
|
|
2682
2809
|
// Listen for events
|
|
2683
2810
|
if (checkWritable) {
|
|
2684
|
-
|
|
2811
|
+
registry.once(normalizedStream, "finish", () => done());
|
|
2685
2812
|
}
|
|
2686
2813
|
if (checkReadable) {
|
|
2687
|
-
|
|
2814
|
+
registry.once(normalizedStream, "end", () => done());
|
|
2688
2815
|
}
|
|
2689
|
-
|
|
2690
|
-
|
|
2816
|
+
registry.once(normalizedStream, "error", (err) => done(err));
|
|
2817
|
+
registry.once(normalizedStream, "close", () => done());
|
|
2691
2818
|
});
|
|
2692
2819
|
// If callback provided, use it
|
|
2693
2820
|
if (cb) {
|
|
@@ -2709,38 +2836,8 @@ async function streamToPromise(stream) {
|
|
|
2709
2836
|
* (Browser equivalent of Node.js streamToBuffer)
|
|
2710
2837
|
*/
|
|
2711
2838
|
async function streamToUint8Array(stream) {
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
iterable = Readable.fromWeb(stream);
|
|
2715
|
-
}
|
|
2716
|
-
else if (isAsyncIterable(stream)) {
|
|
2717
|
-
iterable = stream;
|
|
2718
|
-
}
|
|
2719
|
-
else {
|
|
2720
|
-
throw new Error("streamToUint8Array: unsupported stream type");
|
|
2721
|
-
}
|
|
2722
|
-
const chunks = [];
|
|
2723
|
-
let totalLength = 0;
|
|
2724
|
-
for await (const chunk of iterable) {
|
|
2725
|
-
chunks.push(chunk);
|
|
2726
|
-
totalLength += chunk.length;
|
|
2727
|
-
}
|
|
2728
|
-
// Fast paths
|
|
2729
|
-
const len = chunks.length;
|
|
2730
|
-
if (len === 0) {
|
|
2731
|
-
return new Uint8Array(0);
|
|
2732
|
-
}
|
|
2733
|
-
if (len === 1) {
|
|
2734
|
-
return chunks[0];
|
|
2735
|
-
}
|
|
2736
|
-
// Use precalculated total length
|
|
2737
|
-
const result = new Uint8Array(totalLength);
|
|
2738
|
-
let offset = 0;
|
|
2739
|
-
for (let i = 0; i < len; i++) {
|
|
2740
|
-
result.set(chunks[i], offset);
|
|
2741
|
-
offset += chunks[i].length;
|
|
2742
|
-
}
|
|
2743
|
-
return result;
|
|
2839
|
+
const { chunks, totalLength } = await collectStreamChunks(stream);
|
|
2840
|
+
return concatWithLength(chunks, totalLength);
|
|
2744
2841
|
}
|
|
2745
2842
|
/**
|
|
2746
2843
|
* Alias for streamToUint8Array (Node.js compatibility)
|
|
@@ -2750,8 +2847,10 @@ exports.streamToBuffer = streamToUint8Array;
|
|
|
2750
2847
|
* Collect all data from a readable stream into a string
|
|
2751
2848
|
*/
|
|
2752
2849
|
async function streamToString(stream, encoding) {
|
|
2753
|
-
const
|
|
2754
|
-
|
|
2850
|
+
const { chunks, totalLength } = await collectStreamChunks(stream);
|
|
2851
|
+
const combined = concatWithLength(chunks, totalLength);
|
|
2852
|
+
const decoder = encoding ? (0, shared_1.getTextDecoder)(encoding) : shared_1.textDecoder;
|
|
2853
|
+
return decoder.decode(combined);
|
|
2755
2854
|
}
|
|
2756
2855
|
/**
|
|
2757
2856
|
* Drain a stream (consume all data without processing)
|
|
@@ -2839,14 +2938,19 @@ function addAbortSignal(signal, stream) {
|
|
|
2839
2938
|
stream.destroy(new Error("Aborted"));
|
|
2840
2939
|
return stream;
|
|
2841
2940
|
}
|
|
2941
|
+
const cleanup = () => {
|
|
2942
|
+
signal.removeEventListener("abort", onAbort);
|
|
2943
|
+
removeEmitterListener(stream, "close", onClose);
|
|
2944
|
+
};
|
|
2842
2945
|
const onAbort = () => {
|
|
2946
|
+
cleanup();
|
|
2843
2947
|
stream.destroy(new Error("Aborted"));
|
|
2844
2948
|
};
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
});
|
|
2949
|
+
const onClose = () => {
|
|
2950
|
+
cleanup();
|
|
2951
|
+
};
|
|
2952
|
+
signal.addEventListener("abort", onAbort);
|
|
2953
|
+
addEmitterListener(stream, "close", onClose, { once: true });
|
|
2850
2954
|
return stream;
|
|
2851
2955
|
}
|
|
2852
2956
|
/**
|
|
@@ -2855,60 +2959,68 @@ function addAbortSignal(signal, stream) {
|
|
|
2855
2959
|
function createDuplex(options) {
|
|
2856
2960
|
const readableObjectMode = options?.readableObjectMode ?? options?.objectMode;
|
|
2857
2961
|
const writableObjectMode = options?.writableObjectMode ?? options?.objectMode;
|
|
2962
|
+
const underlyingWritable = options?.writable;
|
|
2858
2963
|
const duplex = new Duplex({
|
|
2859
2964
|
allowHalfOpen: options?.allowHalfOpen,
|
|
2860
2965
|
readableHighWaterMark: options?.readableHighWaterMark,
|
|
2861
2966
|
writableHighWaterMark: options?.writableHighWaterMark,
|
|
2862
2967
|
readableObjectMode,
|
|
2863
|
-
writableObjectMode
|
|
2968
|
+
writableObjectMode,
|
|
2969
|
+
read: options?.read,
|
|
2970
|
+
write: options?.write ??
|
|
2971
|
+
(underlyingWritable
|
|
2972
|
+
? (chunk, encoding, callback) => {
|
|
2973
|
+
if (typeof underlyingWritable.write === "function") {
|
|
2974
|
+
underlyingWritable.write(chunk, encoding, callback);
|
|
2975
|
+
return;
|
|
2976
|
+
}
|
|
2977
|
+
// Best-effort sync sink
|
|
2978
|
+
try {
|
|
2979
|
+
underlyingWritable.write?.(chunk);
|
|
2980
|
+
callback(null);
|
|
2981
|
+
}
|
|
2982
|
+
catch (err) {
|
|
2983
|
+
callback(err);
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
: undefined),
|
|
2987
|
+
final: options?.final ??
|
|
2988
|
+
(underlyingWritable
|
|
2989
|
+
? (callback) => {
|
|
2990
|
+
if (typeof underlyingWritable.end === "function") {
|
|
2991
|
+
underlyingWritable.end((err) => callback(err ?? null));
|
|
2992
|
+
}
|
|
2993
|
+
else {
|
|
2994
|
+
underlyingWritable.end?.();
|
|
2995
|
+
callback(null);
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
: undefined)
|
|
2864
2999
|
});
|
|
2865
|
-
// If
|
|
3000
|
+
// If an underlying readable is provided, forward it into the duplex readable side.
|
|
2866
3001
|
if (options?.readable) {
|
|
2867
3002
|
const readable = options.readable;
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
duplex.end = function (chunkOrCallback, encodingOrCallback, callback) {
|
|
2890
|
-
const cb = typeof chunkOrCallback === "function"
|
|
2891
|
-
? chunkOrCallback
|
|
2892
|
-
: typeof encodingOrCallback === "function"
|
|
2893
|
-
? encodingOrCallback
|
|
2894
|
-
: (callback ?? (() => { }));
|
|
2895
|
-
if (chunkOrCallback !== undefined && typeof chunkOrCallback !== "function") {
|
|
2896
|
-
duplex.write(chunkOrCallback);
|
|
2897
|
-
}
|
|
2898
|
-
// Call custom final handler
|
|
2899
|
-
options.final.call(duplex, (err) => {
|
|
2900
|
-
if (err) {
|
|
2901
|
-
duplex.emit("error", err);
|
|
2902
|
-
}
|
|
2903
|
-
else {
|
|
2904
|
-
duplex.emit("finish");
|
|
2905
|
-
}
|
|
2906
|
-
// Call original end to properly close writable side
|
|
2907
|
-
originalEnd();
|
|
2908
|
-
cb();
|
|
2909
|
-
});
|
|
2910
|
-
return duplex;
|
|
2911
|
-
};
|
|
3003
|
+
const sink = new Writable({
|
|
3004
|
+
objectMode: duplex.readableObjectMode,
|
|
3005
|
+
write(chunk, _encoding, callback) {
|
|
3006
|
+
duplex.push(chunk);
|
|
3007
|
+
callback(null);
|
|
3008
|
+
},
|
|
3009
|
+
final(callback) {
|
|
3010
|
+
duplex.push(null);
|
|
3011
|
+
callback(null);
|
|
3012
|
+
}
|
|
3013
|
+
});
|
|
3014
|
+
if (typeof readable?.on === "function") {
|
|
3015
|
+
const onError = (err) => {
|
|
3016
|
+
duplex.destroy(err);
|
|
3017
|
+
};
|
|
3018
|
+
const cleanupError = addEmitterListener(readable, "error", onError);
|
|
3019
|
+
addEmitterListener(readable, "end", cleanupError, { once: true });
|
|
3020
|
+
addEmitterListener(readable, "close", cleanupError, { once: true });
|
|
3021
|
+
addEmitterListener(sink, "finish", cleanupError, { once: true });
|
|
3022
|
+
}
|
|
3023
|
+
readable.pipe?.(sink);
|
|
2912
3024
|
}
|
|
2913
3025
|
if (options?.destroy) {
|
|
2914
3026
|
const originalDestroy = duplex.destroy.bind(duplex);
|
|
@@ -2932,20 +3044,7 @@ function createDuplex(options) {
|
|
|
2932
3044
|
*/
|
|
2933
3045
|
function createReadableFromGenerator(generator, options) {
|
|
2934
3046
|
const readable = new Readable({ ...options, objectMode: options?.objectMode ?? true });
|
|
2935
|
-
(
|
|
2936
|
-
try {
|
|
2937
|
-
for await (const chunk of generator()) {
|
|
2938
|
-
if (!readable.push(chunk)) {
|
|
2939
|
-
// Backpressure
|
|
2940
|
-
await new Promise(resolve => setTimeout(resolve, 0));
|
|
2941
|
-
}
|
|
2942
|
-
}
|
|
2943
|
-
readable.push(null);
|
|
2944
|
-
}
|
|
2945
|
-
catch (err) {
|
|
2946
|
-
readable.destroy(err);
|
|
2947
|
-
}
|
|
2948
|
-
})();
|
|
3047
|
+
pumpAsyncIterableToReadable(readable, generator());
|
|
2949
3048
|
return readable;
|
|
2950
3049
|
}
|
|
2951
3050
|
/**
|
|
@@ -2975,8 +3074,8 @@ function compose(...transforms) {
|
|
|
2975
3074
|
transform: chunk => chunk
|
|
2976
3075
|
});
|
|
2977
3076
|
}
|
|
2978
|
-
|
|
2979
|
-
if (len === 1
|
|
3077
|
+
// Preserve identity: compose(single) returns the same transform.
|
|
3078
|
+
if (len === 1) {
|
|
2980
3079
|
return transforms[0];
|
|
2981
3080
|
}
|
|
2982
3081
|
// Chain the transforms: first → second → ... → last
|
|
@@ -2987,19 +3086,34 @@ function compose(...transforms) {
|
|
|
2987
3086
|
transforms[i].pipe(transforms[i + 1]);
|
|
2988
3087
|
}
|
|
2989
3088
|
class ComposedTransform extends Transform {
|
|
2990
|
-
constructor() {
|
|
2991
|
-
super(
|
|
3089
|
+
constructor(options) {
|
|
3090
|
+
super(options);
|
|
2992
3091
|
this._dataForwarding = false;
|
|
2993
3092
|
this._endForwarding = false;
|
|
3093
|
+
this._dataForwardCleanup = null;
|
|
3094
|
+
this._endForwardCleanup = null;
|
|
3095
|
+
this._errorForwardCleanup = [];
|
|
3096
|
+
for (const t of transforms) {
|
|
3097
|
+
const onError = (err) => {
|
|
3098
|
+
this.emit("error", err);
|
|
3099
|
+
};
|
|
3100
|
+
this._errorForwardCleanup.push(addEmitterListener(t, "error", onError));
|
|
3101
|
+
}
|
|
2994
3102
|
}
|
|
2995
3103
|
on(event, listener) {
|
|
2996
3104
|
if (event === "data" && !this._dataForwarding) {
|
|
2997
3105
|
this._dataForwarding = true;
|
|
2998
|
-
|
|
3106
|
+
const onData = (chunk) => {
|
|
3107
|
+
this.emit("data", chunk);
|
|
3108
|
+
};
|
|
3109
|
+
this._dataForwardCleanup = addEmitterListener(last, "data", onData);
|
|
2999
3110
|
}
|
|
3000
3111
|
if (event === "end" && !this._endForwarding) {
|
|
3001
3112
|
this._endForwarding = true;
|
|
3002
|
-
|
|
3113
|
+
const onEnd = () => {
|
|
3114
|
+
this.emit("end");
|
|
3115
|
+
};
|
|
3116
|
+
this._endForwardCleanup = addEmitterListener(last, "end", onEnd, { once: true });
|
|
3003
3117
|
}
|
|
3004
3118
|
return super.on(event, listener);
|
|
3005
3119
|
}
|
|
@@ -3025,6 +3139,18 @@ function compose(...transforms) {
|
|
|
3025
3139
|
return last.pipe(destination);
|
|
3026
3140
|
}
|
|
3027
3141
|
destroy(error) {
|
|
3142
|
+
if (this._dataForwardCleanup) {
|
|
3143
|
+
this._dataForwardCleanup();
|
|
3144
|
+
this._dataForwardCleanup = null;
|
|
3145
|
+
}
|
|
3146
|
+
if (this._endForwardCleanup) {
|
|
3147
|
+
this._endForwardCleanup();
|
|
3148
|
+
this._endForwardCleanup = null;
|
|
3149
|
+
}
|
|
3150
|
+
for (let i = this._errorForwardCleanup.length - 1; i >= 0; i--) {
|
|
3151
|
+
this._errorForwardCleanup[i]();
|
|
3152
|
+
}
|
|
3153
|
+
this._errorForwardCleanup.length = 0;
|
|
3028
3154
|
for (const t of transforms) {
|
|
3029
3155
|
t.destroy(error);
|
|
3030
3156
|
}
|
|
@@ -3048,12 +3174,6 @@ function compose(...transforms) {
|
|
|
3048
3174
|
objectMode: first?.objectMode ?? true,
|
|
3049
3175
|
transform: chunk => chunk
|
|
3050
3176
|
});
|
|
3051
|
-
// Forward errors from any transform
|
|
3052
|
-
for (const t of transforms) {
|
|
3053
|
-
t.on("error", (err) => {
|
|
3054
|
-
composed.emit("error", err);
|
|
3055
|
-
});
|
|
3056
|
-
}
|
|
3057
3177
|
// Reflect underlying readability/writability like the previous duck-typed wrapper
|
|
3058
3178
|
Object.defineProperty(composed, "readable", {
|
|
3059
3179
|
get: () => last.readable
|