@cj-tech-master/excelts 4.2.1-canary.20260111102127.f808a37 → 4.2.1
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 +31 -0
- package/dist/browser/index.browser.d.ts +0 -1
- package/dist/browser/index.browser.js +0 -12
- package/dist/browser/modules/archive/byte-queue.d.ts +18 -0
- package/dist/browser/modules/archive/byte-queue.js +125 -0
- package/dist/browser/modules/archive/{compression/compress.base.js → compress.base.js} +1 -1
- package/dist/browser/modules/archive/{compression/compress.browser.d.ts → compress.browser.d.ts} +8 -2
- package/dist/{esm/modules/archive/compression → browser/modules/archive}/compress.browser.js +11 -3
- package/dist/browser/modules/archive/{compression/compress.d.ts → compress.d.ts} +2 -2
- package/dist/{esm/modules/archive/compression → browser/modules/archive}/compress.js +1 -1
- package/dist/browser/modules/archive/{compression/crc32.browser.d.ts → crc32.browser.d.ts} +1 -1
- package/dist/browser/modules/archive/{compression/crc32.d.ts → crc32.d.ts} +1 -1
- package/dist/browser/modules/archive/{compression/crc32.js → crc32.js} +1 -1
- package/dist/browser/modules/archive/defaults.d.ts +0 -1
- package/dist/browser/modules/archive/defaults.js +3 -6
- package/dist/browser/modules/archive/{compression/deflate-fallback.js → deflate-fallback.js} +1 -1
- package/dist/browser/modules/archive/{unzip/extract.d.ts → extract.d.ts} +2 -2
- package/dist/browser/modules/archive/index.base.d.ts +4 -4
- package/dist/browser/modules/archive/index.base.js +6 -3
- package/dist/browser/modules/archive/index.browser.d.ts +4 -3
- package/dist/browser/modules/archive/index.browser.js +7 -3
- package/dist/browser/modules/archive/index.d.ts +4 -3
- package/dist/browser/modules/archive/index.js +5 -3
- package/dist/browser/modules/archive/{unzip/stream.base.d.ts → parse.base.d.ts} +2 -36
- package/dist/browser/modules/archive/parse.base.js +644 -0
- package/dist/browser/modules/archive/{unzip/stream.browser.d.ts → parse.browser.d.ts} +1 -1
- package/dist/{esm/modules/archive/unzip/stream.browser.js → browser/modules/archive/parse.browser.js} +110 -371
- package/dist/browser/modules/archive/{unzip/stream.d.ts → parse.d.ts} +2 -2
- package/dist/{esm/modules/archive/unzip/stream.js → browser/modules/archive/parse.js} +5 -6
- package/dist/browser/modules/archive/{compression/streaming-compress.browser.d.ts → streaming-compress.browser.d.ts} +2 -2
- package/dist/browser/modules/archive/{compression/streaming-compress.browser.js → streaming-compress.browser.js} +3 -3
- package/dist/browser/modules/archive/{compression/streaming-compress.d.ts → streaming-compress.d.ts} +2 -2
- package/dist/browser/modules/archive/{compression/streaming-compress.js → streaming-compress.js} +2 -2
- package/dist/browser/modules/archive/{zip/stream.d.ts → streaming-zip.d.ts} +5 -28
- package/dist/{esm/modules/archive/zip/stream.js → browser/modules/archive/streaming-zip.js} +48 -192
- package/dist/browser/modules/archive/utils/bytes.js +16 -16
- package/dist/browser/modules/archive/utils/parse-buffer.js +23 -21
- package/dist/browser/modules/archive/utils/timestamps.js +1 -62
- package/dist/browser/modules/archive/utils/zip-extra-fields.d.ts +1 -1
- package/dist/browser/modules/archive/utils/zip-extra-fields.js +14 -26
- package/dist/browser/modules/archive/utils/zip-extra.d.ts +18 -0
- package/dist/browser/modules/archive/utils/zip-extra.js +68 -0
- package/dist/browser/modules/archive/zip-builder.d.ts +117 -0
- package/dist/browser/modules/archive/zip-builder.js +292 -0
- package/dist/browser/modules/archive/zip-constants.d.ts +18 -0
- package/dist/browser/modules/archive/zip-constants.js +23 -0
- package/dist/{esm/modules/archive/zip → browser/modules/archive}/zip-entry-metadata.js +3 -3
- package/dist/{types/modules/archive/unzip → browser/modules/archive}/zip-parser.d.ts +1 -1
- package/dist/{esm/modules/archive/unzip → browser/modules/archive}/zip-parser.js +24 -38
- package/dist/browser/modules/archive/{zip-spec/zip-records.d.ts → zip-records.d.ts} +0 -20
- package/dist/browser/modules/archive/zip-records.js +84 -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 +6 -3
- package/dist/browser/modules/excel/xlsx/xlsx.js +1 -1
- package/dist/browser/modules/stream/streams.browser.d.ts +30 -28
- package/dist/browser/modules/stream/streams.browser.js +710 -830
- package/dist/browser/modules/stream/streams.js +58 -140
- package/dist/cjs/modules/archive/byte-queue.js +129 -0
- package/dist/cjs/modules/archive/{compression/compress.base.js → compress.base.js} +1 -1
- package/dist/cjs/modules/archive/{compression/compress.browser.js → compress.browser.js} +11 -3
- package/dist/cjs/modules/archive/{compression/compress.js → compress.js} +1 -1
- package/dist/cjs/modules/archive/{compression/crc32.js → crc32.js} +1 -1
- package/dist/cjs/modules/archive/defaults.js +4 -7
- package/dist/cjs/modules/archive/{compression/deflate-fallback.js → deflate-fallback.js} +1 -1
- package/dist/cjs/modules/archive/index.base.js +19 -9
- package/dist/cjs/modules/archive/index.browser.js +10 -4
- package/dist/cjs/modules/archive/index.js +8 -4
- package/dist/cjs/modules/archive/parse.base.js +666 -0
- package/dist/cjs/modules/archive/{unzip/stream.browser.js → parse.browser.js} +111 -372
- package/dist/cjs/modules/archive/{unzip/stream.js → parse.js} +8 -9
- package/dist/cjs/modules/archive/{compression/streaming-compress.browser.js → streaming-compress.browser.js} +3 -3
- package/dist/cjs/modules/archive/{compression/streaming-compress.js → streaming-compress.js} +2 -2
- package/dist/cjs/modules/archive/{zip/stream.js → streaming-zip.js} +50 -194
- package/dist/cjs/modules/archive/utils/bytes.js +16 -16
- package/dist/cjs/modules/archive/utils/parse-buffer.js +23 -21
- package/dist/cjs/modules/archive/utils/timestamps.js +3 -64
- package/dist/cjs/modules/archive/utils/zip-extra-fields.js +14 -26
- package/dist/cjs/modules/archive/utils/zip-extra.js +74 -0
- package/dist/cjs/modules/archive/zip-builder.js +297 -0
- package/dist/cjs/modules/archive/zip-constants.js +26 -0
- package/dist/cjs/modules/archive/{zip/zip-entry-metadata.js → zip-entry-metadata.js} +5 -5
- package/dist/cjs/modules/archive/{unzip/zip-parser.js → zip-parser.js} +33 -47
- package/dist/cjs/modules/archive/zip-records.js +90 -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 +9 -6
- package/dist/cjs/modules/excel/xlsx/xlsx.js +2 -2
- package/dist/cjs/modules/stream/streams.browser.js +710 -830
- package/dist/cjs/modules/stream/streams.js +58 -140
- package/dist/esm/index.browser.js +0 -12
- package/dist/esm/modules/archive/byte-queue.js +125 -0
- package/dist/esm/modules/archive/{compression/compress.base.js → compress.base.js} +1 -1
- package/dist/{browser/modules/archive/compression → esm/modules/archive}/compress.browser.js +11 -3
- package/dist/{browser/modules/archive/compression → esm/modules/archive}/compress.js +1 -1
- package/dist/esm/modules/archive/{compression/crc32.js → crc32.js} +1 -1
- package/dist/esm/modules/archive/defaults.js +3 -6
- package/dist/esm/modules/archive/{compression/deflate-fallback.js → deflate-fallback.js} +1 -1
- package/dist/esm/modules/archive/index.base.js +6 -3
- package/dist/esm/modules/archive/index.browser.js +7 -3
- package/dist/esm/modules/archive/index.js +5 -3
- package/dist/esm/modules/archive/parse.base.js +644 -0
- package/dist/{browser/modules/archive/unzip/stream.browser.js → esm/modules/archive/parse.browser.js} +110 -371
- package/dist/{browser/modules/archive/unzip/stream.js → esm/modules/archive/parse.js} +5 -6
- package/dist/esm/modules/archive/{compression/streaming-compress.browser.js → streaming-compress.browser.js} +3 -3
- package/dist/esm/modules/archive/{compression/streaming-compress.js → streaming-compress.js} +2 -2
- package/dist/{browser/modules/archive/zip/stream.js → esm/modules/archive/streaming-zip.js} +48 -192
- package/dist/esm/modules/archive/utils/bytes.js +16 -16
- package/dist/esm/modules/archive/utils/parse-buffer.js +23 -21
- package/dist/esm/modules/archive/utils/timestamps.js +1 -62
- package/dist/esm/modules/archive/utils/zip-extra-fields.js +14 -26
- package/dist/esm/modules/archive/utils/zip-extra.js +68 -0
- package/dist/esm/modules/archive/zip-builder.js +292 -0
- package/dist/esm/modules/archive/zip-constants.js +23 -0
- package/dist/{browser/modules/archive/zip → esm/modules/archive}/zip-entry-metadata.js +3 -3
- package/dist/{browser/modules/archive/unzip → esm/modules/archive}/zip-parser.js +24 -38
- package/dist/esm/modules/archive/zip-records.js +84 -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 +6 -3
- package/dist/esm/modules/excel/xlsx/xlsx.js +1 -1
- package/dist/esm/modules/stream/streams.browser.js +710 -830
- package/dist/esm/modules/stream/streams.js +58 -140
- package/dist/iife/THIRD_PARTY_NOTICES.md +31 -0
- package/dist/iife/excelts.iife.js +4425 -6215
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +31 -103
- package/dist/types/index.browser.d.ts +0 -1
- package/dist/types/modules/archive/byte-queue.d.ts +18 -0
- package/dist/types/modules/archive/{compression/compress.browser.d.ts → compress.browser.d.ts} +8 -2
- package/dist/types/modules/archive/defaults.d.ts +0 -1
- package/dist/types/modules/archive/index.base.d.ts +4 -4
- package/dist/types/modules/archive/index.browser.d.ts +4 -3
- package/dist/types/modules/archive/index.d.ts +4 -3
- package/dist/types/modules/archive/{unzip/stream.base.d.ts → parse.base.d.ts} +4 -38
- package/dist/types/modules/archive/{unzip/stream.browser.d.ts → parse.browser.d.ts} +2 -2
- package/dist/types/modules/archive/{unzip/stream.d.ts → parse.d.ts} +3 -3
- package/dist/types/modules/archive/{compression/streaming-compress.browser.d.ts → streaming-compress.browser.d.ts} +1 -1
- package/dist/types/modules/archive/{zip/stream.d.ts → streaming-zip.d.ts} +6 -29
- package/dist/types/modules/archive/utils/zip-extra-fields.d.ts +1 -1
- package/dist/types/modules/archive/utils/zip-extra.d.ts +18 -0
- package/dist/types/modules/archive/zip-builder.d.ts +117 -0
- package/dist/types/modules/archive/zip-constants.d.ts +18 -0
- package/dist/types/modules/archive/{zip/zip-entry-metadata.d.ts → zip-entry-metadata.d.ts} +1 -1
- package/dist/{browser/modules/archive/unzip → types/modules/archive}/zip-parser.d.ts +1 -1
- package/dist/types/modules/archive/{zip-spec/zip-records.d.ts → zip-records.d.ts} +0 -20
- package/dist/types/modules/excel/stream/workbook-writer.browser.d.ts +1 -1
- package/dist/types/modules/stream/streams.browser.d.ts +30 -28
- package/package.json +1 -5
- package/dist/browser/modules/archive/internal/byte-queue.d.ts +0 -33
- package/dist/browser/modules/archive/internal/byte-queue.js +0 -407
- package/dist/browser/modules/archive/io/archive-sink.d.ts +0 -9
- package/dist/browser/modules/archive/io/archive-sink.js +0 -77
- package/dist/browser/modules/archive/io/archive-source.d.ts +0 -8
- package/dist/browser/modules/archive/io/archive-source.js +0 -107
- package/dist/browser/modules/archive/unzip/index.d.ts +0 -40
- package/dist/browser/modules/archive/unzip/index.js +0 -164
- package/dist/browser/modules/archive/unzip/stream.base.js +0 -1022
- package/dist/browser/modules/archive/utils/async-queue.d.ts +0 -7
- package/dist/browser/modules/archive/utils/async-queue.js +0 -103
- package/dist/browser/modules/archive/utils/compressibility.d.ts +0 -10
- package/dist/browser/modules/archive/utils/compressibility.js +0 -57
- package/dist/browser/modules/archive/utils/pattern-scanner.d.ts +0 -21
- package/dist/browser/modules/archive/utils/pattern-scanner.js +0 -27
- package/dist/browser/modules/archive/zip/index.d.ts +0 -42
- package/dist/browser/modules/archive/zip/index.js +0 -157
- package/dist/browser/modules/archive/zip/zip-bytes.d.ts +0 -73
- package/dist/browser/modules/archive/zip/zip-bytes.js +0 -239
- package/dist/browser/modules/archive/zip-spec/zip-records.js +0 -126
- package/dist/cjs/modules/archive/internal/byte-queue.js +0 -411
- package/dist/cjs/modules/archive/io/archive-sink.js +0 -82
- package/dist/cjs/modules/archive/io/archive-source.js +0 -114
- package/dist/cjs/modules/archive/unzip/index.js +0 -170
- package/dist/cjs/modules/archive/unzip/stream.base.js +0 -1044
- package/dist/cjs/modules/archive/utils/async-queue.js +0 -106
- package/dist/cjs/modules/archive/utils/compressibility.js +0 -60
- package/dist/cjs/modules/archive/utils/pattern-scanner.js +0 -31
- package/dist/cjs/modules/archive/zip/index.js +0 -162
- package/dist/cjs/modules/archive/zip/zip-bytes.js +0 -242
- package/dist/cjs/modules/archive/zip-spec/zip-records.js +0 -136
- package/dist/esm/modules/archive/internal/byte-queue.js +0 -407
- package/dist/esm/modules/archive/io/archive-sink.js +0 -77
- package/dist/esm/modules/archive/io/archive-source.js +0 -107
- package/dist/esm/modules/archive/unzip/index.js +0 -164
- package/dist/esm/modules/archive/unzip/stream.base.js +0 -1022
- package/dist/esm/modules/archive/utils/async-queue.js +0 -103
- package/dist/esm/modules/archive/utils/compressibility.js +0 -57
- package/dist/esm/modules/archive/utils/pattern-scanner.js +0 -27
- package/dist/esm/modules/archive/zip/index.js +0 -157
- package/dist/esm/modules/archive/zip/zip-bytes.js +0 -239
- package/dist/esm/modules/archive/zip-spec/zip-records.js +0 -126
- package/dist/types/modules/archive/internal/byte-queue.d.ts +0 -33
- package/dist/types/modules/archive/io/archive-sink.d.ts +0 -9
- package/dist/types/modules/archive/io/archive-source.d.ts +0 -8
- package/dist/types/modules/archive/unzip/index.d.ts +0 -40
- package/dist/types/modules/archive/utils/async-queue.d.ts +0 -7
- package/dist/types/modules/archive/utils/compressibility.d.ts +0 -10
- package/dist/types/modules/archive/utils/pattern-scanner.d.ts +0 -21
- package/dist/types/modules/archive/zip/index.d.ts +0 -42
- package/dist/types/modules/archive/zip/zip-bytes.d.ts +0 -73
- /package/dist/browser/modules/archive/{compression/compress.base.d.ts → compress.base.d.ts} +0 -0
- /package/dist/browser/modules/archive/{compression/crc32.base.d.ts → crc32.base.d.ts} +0 -0
- /package/dist/browser/modules/archive/{compression/crc32.base.js → crc32.base.js} +0 -0
- /package/dist/browser/modules/archive/{compression/crc32.browser.js → crc32.browser.js} +0 -0
- /package/dist/browser/modules/archive/{compression/deflate-fallback.d.ts → deflate-fallback.d.ts} +0 -0
- /package/dist/browser/modules/archive/{unzip/extract.js → extract.js} +0 -0
- /package/dist/browser/modules/archive/{compression/streaming-compress.base.d.ts → streaming-compress.base.d.ts} +0 -0
- /package/dist/browser/modules/archive/{compression/streaming-compress.base.js → streaming-compress.base.js} +0 -0
- /package/dist/browser/modules/archive/{zip-spec/zip-entry-info.d.ts → zip-entry-info.d.ts} +0 -0
- /package/dist/browser/modules/archive/{zip-spec/zip-entry-info.js → zip-entry-info.js} +0 -0
- /package/dist/browser/modules/archive/{zip/zip-entry-metadata.d.ts → zip-entry-metadata.d.ts} +0 -0
- /package/dist/cjs/modules/archive/{compression/crc32.base.js → crc32.base.js} +0 -0
- /package/dist/cjs/modules/archive/{compression/crc32.browser.js → crc32.browser.js} +0 -0
- /package/dist/cjs/modules/archive/{unzip/extract.js → extract.js} +0 -0
- /package/dist/cjs/modules/archive/{compression/streaming-compress.base.js → streaming-compress.base.js} +0 -0
- /package/dist/cjs/modules/archive/{zip-spec/zip-entry-info.js → zip-entry-info.js} +0 -0
- /package/dist/esm/modules/archive/{compression/crc32.base.js → crc32.base.js} +0 -0
- /package/dist/esm/modules/archive/{compression/crc32.browser.js → crc32.browser.js} +0 -0
- /package/dist/esm/modules/archive/{unzip/extract.js → extract.js} +0 -0
- /package/dist/esm/modules/archive/{compression/streaming-compress.base.js → streaming-compress.base.js} +0 -0
- /package/dist/esm/modules/archive/{zip-spec/zip-entry-info.js → zip-entry-info.js} +0 -0
- /package/dist/types/modules/archive/{compression/compress.base.d.ts → compress.base.d.ts} +0 -0
- /package/dist/types/modules/archive/{compression/compress.d.ts → compress.d.ts} +0 -0
- /package/dist/types/modules/archive/{compression/crc32.base.d.ts → crc32.base.d.ts} +0 -0
- /package/dist/types/modules/archive/{compression/crc32.browser.d.ts → crc32.browser.d.ts} +0 -0
- /package/dist/types/modules/archive/{compression/crc32.d.ts → crc32.d.ts} +0 -0
- /package/dist/types/modules/archive/{compression/deflate-fallback.d.ts → deflate-fallback.d.ts} +0 -0
- /package/dist/types/modules/archive/{unzip/extract.d.ts → extract.d.ts} +0 -0
- /package/dist/types/modules/archive/{compression/streaming-compress.base.d.ts → streaming-compress.base.d.ts} +0 -0
- /package/dist/types/modules/archive/{compression/streaming-compress.d.ts → streaming-compress.d.ts} +0 -0
- /package/dist/types/modules/archive/{zip-spec/zip-entry-info.d.ts → zip-entry-info.d.ts} +0 -0
|
@@ -55,40 +55,6 @@ 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
|
-
};
|
|
92
58
|
// =============================================================================
|
|
93
59
|
// Readable Stream Wrapper
|
|
94
60
|
// =============================================================================
|
|
@@ -105,7 +71,6 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
105
71
|
this._bufferSize = 0;
|
|
106
72
|
this._reading = false;
|
|
107
73
|
this._ended = false;
|
|
108
|
-
this._endEmitted = false;
|
|
109
74
|
this._destroyed = false;
|
|
110
75
|
this._errored = null;
|
|
111
76
|
this._closed = false;
|
|
@@ -162,7 +127,20 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
162
127
|
*/
|
|
163
128
|
static from(iterable, options) {
|
|
164
129
|
const readable = new Readable({ ...options, objectMode: options?.objectMode ?? true });
|
|
165
|
-
|
|
130
|
+
(async () => {
|
|
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
|
+
})();
|
|
166
144
|
return readable;
|
|
167
145
|
}
|
|
168
146
|
/**
|
|
@@ -207,30 +185,16 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
207
185
|
// Controller may already be closed
|
|
208
186
|
}
|
|
209
187
|
}
|
|
210
|
-
|
|
211
|
-
// This avoids premature 'end' when producers push null while paused.
|
|
212
|
-
if (this._bufferedLength() === 0) {
|
|
213
|
-
this._emitEndOnce();
|
|
214
|
-
}
|
|
188
|
+
this.emit("end");
|
|
215
189
|
// Note: Don't call destroy() here, let the stream be consumed naturally
|
|
216
190
|
// The reader will return done:true when it finishes reading
|
|
217
191
|
return false;
|
|
218
192
|
}
|
|
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
|
-
}
|
|
229
193
|
if (this._flowing) {
|
|
230
194
|
// In flowing mode, emit data directly without buffering or enqueueing
|
|
231
195
|
// const chunkStr = chunk instanceof Uint8Array ? new TextDecoder().decode(chunk.slice(0, 50)) : String(chunk).slice(0, 50);
|
|
232
196
|
// console.log(`[Readable#${this._id}.push FLOWING] emit data size:${(chunk as any).length || (chunk as any).byteLength} start:"${chunkStr}"`);
|
|
233
|
-
this.emit("data",
|
|
197
|
+
this.emit("data", chunk);
|
|
234
198
|
// Check if stream was paused during emit (backpressure from consumer)
|
|
235
199
|
if (!this._flowing) {
|
|
236
200
|
return false;
|
|
@@ -255,8 +219,10 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
255
219
|
if (!this.objectMode) {
|
|
256
220
|
this._bufferSize += this._getChunkSize(chunk);
|
|
257
221
|
}
|
|
258
|
-
// NOTE:
|
|
259
|
-
//
|
|
222
|
+
// NOTE: Do NOT enqueue to Web Stream controller here!
|
|
223
|
+
// In push mode, _buffer is the only source of data for data events.
|
|
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.
|
|
260
226
|
// Emit readable event when buffer goes from empty to having data
|
|
261
227
|
if (wasEmpty) {
|
|
262
228
|
queueMicrotask(() => this.emit("readable"));
|
|
@@ -270,13 +236,6 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
270
236
|
return this._bufferSize < this.readableHighWaterMark;
|
|
271
237
|
}
|
|
272
238
|
}
|
|
273
|
-
_emitEndOnce() {
|
|
274
|
-
if (this._endEmitted) {
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
this._endEmitted = true;
|
|
278
|
-
this.emit("end");
|
|
279
|
-
}
|
|
280
239
|
/**
|
|
281
240
|
* Put a chunk back at the front of the buffer
|
|
282
241
|
* Note: unshift is allowed even after end, as it's used to put back already read data
|
|
@@ -301,22 +260,14 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
301
260
|
if (!this.objectMode) {
|
|
302
261
|
this._bufferSize -= this._getChunkSize(chunk);
|
|
303
262
|
}
|
|
304
|
-
|
|
305
|
-
if (this._ended && this._bufferedLength() === 0) {
|
|
306
|
-
queueMicrotask(() => this._emitEndOnce());
|
|
307
|
-
}
|
|
308
|
-
return decoded;
|
|
263
|
+
return this._applyEncoding(chunk);
|
|
309
264
|
}
|
|
310
265
|
// For binary mode, handle size
|
|
311
266
|
const chunk = this._bufferShift();
|
|
312
267
|
if (!this.objectMode) {
|
|
313
268
|
this._bufferSize -= this._getChunkSize(chunk);
|
|
314
269
|
}
|
|
315
|
-
|
|
316
|
-
if (this._ended && this._bufferedLength() === 0) {
|
|
317
|
-
queueMicrotask(() => this._emitEndOnce());
|
|
318
|
-
}
|
|
319
|
-
return decoded;
|
|
270
|
+
return this._applyEncoding(chunk);
|
|
320
271
|
}
|
|
321
272
|
return null;
|
|
322
273
|
}
|
|
@@ -422,11 +373,11 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
422
373
|
if (!this.objectMode) {
|
|
423
374
|
this._bufferSize -= this._getChunkSize(chunk);
|
|
424
375
|
}
|
|
425
|
-
this.emit("data",
|
|
376
|
+
this.emit("data", chunk);
|
|
426
377
|
}
|
|
427
378
|
// If already ended, emit end event
|
|
428
379
|
if (this._ended && this._bufferedLength() === 0) {
|
|
429
|
-
this.
|
|
380
|
+
this.emit("end");
|
|
430
381
|
}
|
|
431
382
|
else if (this._read) {
|
|
432
383
|
// Call user-provided read function asynchronously
|
|
@@ -489,36 +440,26 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
489
440
|
}
|
|
490
441
|
this._pipeTo.push(dest);
|
|
491
442
|
// 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
|
-
};
|
|
505
443
|
const dataListener = (chunk) => {
|
|
506
444
|
// Call destination's write() method (not internal _writable.write())
|
|
507
445
|
// This ensures Transform.write() logic runs properly
|
|
508
446
|
const canWrite = dest.write(chunk);
|
|
509
447
|
if (!canWrite) {
|
|
510
448
|
this.pause();
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
449
|
+
if (typeof eventTarget.once === "function") {
|
|
450
|
+
eventTarget.once("drain", () => this.resume());
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
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
|
+
}
|
|
515
460
|
this.resume();
|
|
516
461
|
};
|
|
517
|
-
eventTarget.on("drain",
|
|
518
|
-
const entry = this._pipeListeners.get(dest);
|
|
519
|
-
if (entry) {
|
|
520
|
-
entry.drain = drainListener;
|
|
521
|
-
}
|
|
462
|
+
eventTarget.on("drain", resumeOnce);
|
|
522
463
|
}
|
|
523
464
|
}
|
|
524
465
|
};
|
|
@@ -538,8 +479,7 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
538
479
|
this._pipeListeners.set(dest, {
|
|
539
480
|
data: dataListener,
|
|
540
481
|
end: endListener,
|
|
541
|
-
error: errorListener
|
|
542
|
-
eventTarget
|
|
482
|
+
error: errorListener
|
|
543
483
|
});
|
|
544
484
|
this.on("data", dataListener);
|
|
545
485
|
this.once("end", endListener);
|
|
@@ -562,14 +502,6 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
562
502
|
this.off("data", listeners.data);
|
|
563
503
|
this.off("end", listeners.end);
|
|
564
504
|
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
|
-
}
|
|
573
505
|
this._pipeListeners.delete(destination);
|
|
574
506
|
}
|
|
575
507
|
}
|
|
@@ -581,14 +513,6 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
581
513
|
this.off("data", listeners.data);
|
|
582
514
|
this.off("end", listeners.end);
|
|
583
515
|
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
|
-
}
|
|
592
516
|
this._pipeListeners.delete(target);
|
|
593
517
|
}
|
|
594
518
|
}
|
|
@@ -607,26 +531,12 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
607
531
|
}
|
|
608
532
|
this._destroyed = true;
|
|
609
533
|
this._ended = true;
|
|
610
|
-
// Ensure we detach from destinations to avoid leaking listeners.
|
|
611
|
-
this.unpipe();
|
|
612
534
|
if (error) {
|
|
613
535
|
this._errored = error;
|
|
614
536
|
this.emit("error", error);
|
|
615
537
|
}
|
|
616
538
|
if (this._reader) {
|
|
617
|
-
|
|
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
|
-
});
|
|
539
|
+
this._reader.cancel().catch(() => { });
|
|
630
540
|
}
|
|
631
541
|
this._closed = true;
|
|
632
542
|
this.emit("close");
|
|
@@ -705,38 +615,18 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
705
615
|
const { done, value } = await this._reader.read();
|
|
706
616
|
// Check _pushMode again after async read - if push() was called, stop reading
|
|
707
617
|
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
|
-
}
|
|
718
618
|
break;
|
|
719
619
|
}
|
|
720
620
|
if (done) {
|
|
721
621
|
this._ended = true;
|
|
722
|
-
this.
|
|
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
|
-
}
|
|
622
|
+
this.emit("end");
|
|
733
623
|
break;
|
|
734
624
|
}
|
|
735
625
|
if (value !== undefined) {
|
|
736
626
|
// In flowing mode, emit data directly without buffering
|
|
737
627
|
// Only buffer if not flowing (paused mode)
|
|
738
628
|
if (this._flowing) {
|
|
739
|
-
this.emit("data",
|
|
629
|
+
this.emit("data", value);
|
|
740
630
|
}
|
|
741
631
|
else {
|
|
742
632
|
this._buffer.push(value);
|
|
@@ -749,16 +639,6 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
749
639
|
}
|
|
750
640
|
catch (err) {
|
|
751
641
|
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
|
-
}
|
|
762
642
|
}
|
|
763
643
|
finally {
|
|
764
644
|
this._reading = false;
|
|
@@ -766,8 +646,7 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
766
646
|
}
|
|
767
647
|
/**
|
|
768
648
|
* Async iterator support
|
|
769
|
-
* Uses
|
|
770
|
-
* This matches Node's behavior more closely (iterator drives flowing mode).
|
|
649
|
+
* Uses Web Stream reader for non-push mode, event-based for push mode
|
|
771
650
|
*/
|
|
772
651
|
async *[Symbol.asyncIterator]() {
|
|
773
652
|
// First yield any buffered data
|
|
@@ -776,124 +655,117 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
776
655
|
if (!this.objectMode) {
|
|
777
656
|
this._bufferSize -= this._getChunkSize(chunk);
|
|
778
657
|
}
|
|
779
|
-
yield
|
|
658
|
+
yield chunk;
|
|
780
659
|
}
|
|
660
|
+
// If already ended, we're done
|
|
781
661
|
if (this._ended) {
|
|
782
662
|
return;
|
|
783
663
|
}
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
resolveNext
|
|
826
|
-
|
|
827
|
-
|
|
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);
|
|
851
|
-
try {
|
|
852
|
-
// Iterator consumption should drive the stream.
|
|
853
|
-
this.resume();
|
|
854
|
-
while (true) {
|
|
855
|
-
if (streamError) {
|
|
856
|
-
throw streamError;
|
|
664
|
+
// For controllable streams (not created from external Web Stream),
|
|
665
|
+
// use event-based iteration since data comes from push() calls
|
|
666
|
+
if (!this._webStreamMode) {
|
|
667
|
+
// Create a promise-based queue for incoming data
|
|
668
|
+
const dataQueue = [];
|
|
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;
|
|
857
709
|
}
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
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;
|
|
864
722
|
}
|
|
865
|
-
if (
|
|
866
|
-
|
|
867
|
-
|
|
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
|
+
}
|
|
868
739
|
}
|
|
869
|
-
yield chunk;
|
|
870
|
-
continue;
|
|
871
740
|
}
|
|
741
|
+
// Check for error after loop
|
|
742
|
+
if (streamError) {
|
|
743
|
+
throw streamError;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
finally {
|
|
747
|
+
this.off("data", dataHandler);
|
|
748
|
+
this.off("end", endHandler);
|
|
749
|
+
this.off("error", errorHandler);
|
|
750
|
+
this.off("close", closeHandler);
|
|
751
|
+
}
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
// For Web Stream mode, use the underlying reader
|
|
755
|
+
if (!this._reader) {
|
|
756
|
+
this._reader = this._stream.getReader();
|
|
757
|
+
}
|
|
758
|
+
try {
|
|
759
|
+
while (true) {
|
|
760
|
+
const { done, value } = await this._reader.read();
|
|
872
761
|
if (done) {
|
|
873
762
|
break;
|
|
874
763
|
}
|
|
875
|
-
|
|
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;
|
|
764
|
+
yield value;
|
|
890
765
|
}
|
|
891
766
|
}
|
|
892
767
|
finally {
|
|
893
|
-
this.
|
|
894
|
-
this.off("end", endHandler);
|
|
895
|
-
this.off("error", errorHandler);
|
|
896
|
-
this.off("close", closeHandler);
|
|
768
|
+
this._reader.releaseLock();
|
|
897
769
|
}
|
|
898
770
|
}
|
|
899
771
|
/**
|
|
@@ -1113,32 +985,6 @@ class Readable extends event_emitter_1.EventEmitter {
|
|
|
1113
985
|
}
|
|
1114
986
|
}
|
|
1115
987
|
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
|
-
}
|
|
1142
988
|
// =============================================================================
|
|
1143
989
|
// Writable Stream Wrapper
|
|
1144
990
|
// =============================================================================
|
|
@@ -1156,12 +1002,10 @@ class Writable extends event_emitter_1.EventEmitter {
|
|
|
1156
1002
|
this._closed = false;
|
|
1157
1003
|
this._pendingWrites = 0;
|
|
1158
1004
|
this._writableLength = 0;
|
|
1159
|
-
this._needDrain = false;
|
|
1160
1005
|
this._corked = 0;
|
|
1161
1006
|
this._corkedChunks = [];
|
|
1162
1007
|
this._defaultEncoding = "utf8";
|
|
1163
1008
|
this._aborted = false;
|
|
1164
|
-
this._ownsStream = false;
|
|
1165
1009
|
this.objectMode = options?.objectMode ?? false;
|
|
1166
1010
|
this.writableHighWaterMark = options?.highWaterMark ?? 16384;
|
|
1167
1011
|
this.autoDestroy = options?.autoDestroy ?? true;
|
|
@@ -1177,10 +1021,8 @@ class Writable extends event_emitter_1.EventEmitter {
|
|
|
1177
1021
|
}
|
|
1178
1022
|
if (options?.stream) {
|
|
1179
1023
|
this._stream = options.stream;
|
|
1180
|
-
this._ownsStream = false;
|
|
1181
1024
|
}
|
|
1182
1025
|
else {
|
|
1183
|
-
this._ownsStream = true;
|
|
1184
1026
|
// Create bound references to instance properties/methods for use in WritableStream callbacks
|
|
1185
1027
|
const getWriteFunc = () => this._writeFunc;
|
|
1186
1028
|
const getFinalFunc = () => this._finalFunc;
|
|
@@ -1287,37 +1129,25 @@ class Writable extends event_emitter_1.EventEmitter {
|
|
|
1287
1129
|
this._writableLength += chunkSize;
|
|
1288
1130
|
return this._writableLength < this.writableHighWaterMark;
|
|
1289
1131
|
}
|
|
1290
|
-
|
|
1291
|
-
if (!ok) {
|
|
1292
|
-
this._needDrain = true;
|
|
1293
|
-
}
|
|
1294
|
-
return ok;
|
|
1132
|
+
return this._doWrite(chunk, cb);
|
|
1295
1133
|
}
|
|
1296
1134
|
_doWrite(chunk, callback) {
|
|
1297
1135
|
// Track pending writes for writableLength
|
|
1298
1136
|
const chunkSize = this._getChunkSize(chunk);
|
|
1299
1137
|
this._pendingWrites++;
|
|
1300
1138
|
this._writableLength += chunkSize;
|
|
1301
|
-
|
|
1302
|
-
writer
|
|
1139
|
+
this._getWriter()
|
|
1303
1140
|
.write(chunk)
|
|
1304
1141
|
.then(() => {
|
|
1305
1142
|
this._pendingWrites--;
|
|
1306
1143
|
this._writableLength -= chunkSize;
|
|
1307
|
-
|
|
1308
|
-
this._needDrain = false;
|
|
1309
|
-
this.emit("drain");
|
|
1310
|
-
}
|
|
1144
|
+
this.emit("drain");
|
|
1311
1145
|
callback?.(null);
|
|
1312
1146
|
})
|
|
1313
1147
|
.catch(err => {
|
|
1314
1148
|
this._pendingWrites--;
|
|
1315
1149
|
this._writableLength -= chunkSize;
|
|
1316
|
-
|
|
1317
|
-
if (!this._destroyed) {
|
|
1318
|
-
this._errored = err;
|
|
1319
|
-
this.emit("error", err);
|
|
1320
|
-
}
|
|
1150
|
+
this.emit("error", err);
|
|
1321
1151
|
callback?.(err);
|
|
1322
1152
|
});
|
|
1323
1153
|
// Return false if we've exceeded high water mark (for backpressure)
|
|
@@ -1348,29 +1178,12 @@ class Writable extends event_emitter_1.EventEmitter {
|
|
|
1348
1178
|
: callback;
|
|
1349
1179
|
const finish = async () => {
|
|
1350
1180
|
try {
|
|
1351
|
-
const writer = this._getWriter();
|
|
1352
1181
|
if (chunk !== undefined) {
|
|
1353
|
-
await
|
|
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
|
-
}
|
|
1182
|
+
await this._getWriter().write(chunk);
|
|
1373
1183
|
}
|
|
1184
|
+
await this._getWriter().close();
|
|
1185
|
+
this._finished = true;
|
|
1186
|
+
this.emit("finish");
|
|
1374
1187
|
if (cb) {
|
|
1375
1188
|
cb();
|
|
1376
1189
|
}
|
|
@@ -1391,24 +1204,12 @@ class Writable extends event_emitter_1.EventEmitter {
|
|
|
1391
1204
|
}
|
|
1392
1205
|
this._destroyed = true;
|
|
1393
1206
|
this._ended = true;
|
|
1394
|
-
if (error
|
|
1207
|
+
if (error) {
|
|
1395
1208
|
this._errored = error;
|
|
1396
1209
|
this.emit("error", error);
|
|
1397
1210
|
}
|
|
1398
1211
|
if (this._writer) {
|
|
1399
|
-
|
|
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
|
-
});
|
|
1212
|
+
this._writer.abort(error).catch(() => { });
|
|
1412
1213
|
}
|
|
1413
1214
|
this._closed = true;
|
|
1414
1215
|
this.emit("close");
|
|
@@ -1560,259 +1361,342 @@ function normalizeWritable(stream) {
|
|
|
1560
1361
|
*/
|
|
1561
1362
|
class Transform extends event_emitter_1.EventEmitter {
|
|
1562
1363
|
/**
|
|
1563
|
-
* Push data to the readable side (Node.js compatibility)
|
|
1564
|
-
*
|
|
1364
|
+
* Push data to the readable side (Node.js compatibility)
|
|
1365
|
+
* Can be called from within transform callback
|
|
1565
1366
|
*/
|
|
1566
1367
|
push(chunk) {
|
|
1567
|
-
|
|
1368
|
+
if (chunk === null) {
|
|
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;
|
|
1568
1380
|
}
|
|
1569
1381
|
constructor(options) {
|
|
1570
1382
|
super();
|
|
1571
|
-
this._destroyed = false;
|
|
1572
1383
|
this._ended = false;
|
|
1384
|
+
this._destroyed = false;
|
|
1573
1385
|
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
|
|
1574
1395
|
this._dataForwardingSetup = false;
|
|
1575
|
-
|
|
1576
|
-
this.
|
|
1577
|
-
|
|
1396
|
+
/** @internal - whether we have a data event consumer */
|
|
1397
|
+
this._hasDataConsumer = false;
|
|
1398
|
+
/** @internal - whether we're auto-consuming the readable */
|
|
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 = [];
|
|
1578
1409
|
this.objectMode = options?.objectMode ?? false;
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
this._readable.push(null);
|
|
1595
|
-
callback(null);
|
|
1596
|
-
})
|
|
1597
|
-
.catch(err => callback(err));
|
|
1598
|
-
}
|
|
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;
|
|
1410
|
+
const userTransform = options?.transform;
|
|
1411
|
+
const userFlush = options?.flush;
|
|
1412
|
+
// Create bound references for use in TransformStream callbacks
|
|
1413
|
+
const setController = (ctrl) => {
|
|
1414
|
+
this._transformController = ctrl;
|
|
1415
|
+
};
|
|
1416
|
+
const emitEvent = (event, ...args) => {
|
|
1417
|
+
if (event === "error") {
|
|
1418
|
+
// Only emit error once to prevent duplicate events
|
|
1419
|
+
if (this._errored) {
|
|
1420
|
+
return false;
|
|
1421
|
+
}
|
|
1422
|
+
this._errored = true;
|
|
1423
|
+
// Also destroy the writable to prevent further writes
|
|
1424
|
+
this._writable.destroy(args[0]);
|
|
1630
1425
|
}
|
|
1631
|
-
this.
|
|
1632
|
-
}
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
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;
|
|
1426
|
+
return this.emit(event, ...args);
|
|
1427
|
+
};
|
|
1428
|
+
const getInstance = () => this;
|
|
1429
|
+
// Check if subclass overrides _transform (for Node.js compatibility)
|
|
1430
|
+
// We need to check this at runtime since the subclass constructor runs after super()
|
|
1431
|
+
const hasSubclassTransform = () => {
|
|
1432
|
+
// If userTransform was provided in options, use that
|
|
1433
|
+
if (userTransform) {
|
|
1434
|
+
return false;
|
|
1681
1435
|
}
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1436
|
+
// Check if _transform is overridden (not the base class no-op)
|
|
1437
|
+
const proto = Object.getPrototypeOf(this);
|
|
1438
|
+
return proto._transform !== Transform.prototype._transform;
|
|
1439
|
+
};
|
|
1440
|
+
const hasSubclassFlush = () => {
|
|
1441
|
+
if (userFlush) {
|
|
1442
|
+
return false;
|
|
1686
1443
|
}
|
|
1687
|
-
const
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1444
|
+
const proto = Object.getPrototypeOf(this);
|
|
1445
|
+
return proto._flush !== Transform.prototype._flush;
|
|
1446
|
+
};
|
|
1447
|
+
this._stream = new TransformStream({
|
|
1448
|
+
transform: async (chunk, controller) => {
|
|
1449
|
+
// Skip processing if already errored
|
|
1450
|
+
if (this._errored) {
|
|
1451
|
+
return;
|
|
1452
|
+
}
|
|
1453
|
+
try {
|
|
1454
|
+
// Set controller for push() to use
|
|
1455
|
+
setController(controller);
|
|
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
|
+
});
|
|
1697
1491
|
}
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1492
|
+
else if (transformParamCount === 2) {
|
|
1493
|
+
await new Promise((resolve, reject) => {
|
|
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
|
+
});
|
|
1709
1506
|
}
|
|
1710
|
-
|
|
1711
|
-
|
|
1507
|
+
else {
|
|
1508
|
+
// Simple style: transform(chunk) => result
|
|
1509
|
+
const result = userTransform.call(getInstance(), chunk);
|
|
1510
|
+
if (result && typeof result.then === "function") {
|
|
1511
|
+
const awaitedResult = await result;
|
|
1512
|
+
if (awaitedResult !== undefined) {
|
|
1513
|
+
controller.enqueue(awaitedResult);
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
else {
|
|
1517
|
+
if (result !== undefined) {
|
|
1518
|
+
controller.enqueue(result);
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1712
1521
|
}
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
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);
|
|
1522
|
+
}
|
|
1523
|
+
else {
|
|
1524
|
+
// Default: pass through
|
|
1525
|
+
controller.enqueue(chunk);
|
|
1526
|
+
}
|
|
1723
1527
|
}
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
}
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1528
|
+
catch (err) {
|
|
1529
|
+
controller.error(err);
|
|
1530
|
+
emitEvent("error", err);
|
|
1531
|
+
}
|
|
1532
|
+
finally {
|
|
1533
|
+
setController(null);
|
|
1534
|
+
}
|
|
1535
|
+
},
|
|
1536
|
+
flush: async (controller) => {
|
|
1537
|
+
try {
|
|
1538
|
+
setController(controller);
|
|
1539
|
+
// Check for subclass _flush override first
|
|
1540
|
+
if (hasSubclassFlush()) {
|
|
1541
|
+
await new Promise((resolve, reject) => {
|
|
1542
|
+
this._flush((err, data) => {
|
|
1543
|
+
if (err) {
|
|
1544
|
+
reject(err);
|
|
1545
|
+
}
|
|
1546
|
+
else {
|
|
1547
|
+
if (data !== undefined) {
|
|
1548
|
+
controller.enqueue(data);
|
|
1549
|
+
}
|
|
1550
|
+
resolve();
|
|
1551
|
+
}
|
|
1552
|
+
});
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
1555
|
+
else if (userFlush) {
|
|
1556
|
+
const flushParamCount = userFlush.length;
|
|
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
|
+
});
|
|
1766
1572
|
}
|
|
1767
|
-
|
|
1768
|
-
|
|
1573
|
+
else {
|
|
1574
|
+
// Simple style: flush() => result
|
|
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
|
+
}
|
|
1769
1587
|
}
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
});
|
|
1773
|
-
return;
|
|
1774
|
-
}
|
|
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);
|
|
1588
|
+
}
|
|
1589
|
+
// No flush defined - nothing to do
|
|
1780
1590
|
}
|
|
1781
|
-
|
|
1591
|
+
catch (err) {
|
|
1592
|
+
controller.error(err);
|
|
1593
|
+
emitEvent("error", err);
|
|
1594
|
+
}
|
|
1595
|
+
finally {
|
|
1596
|
+
setController(null);
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
});
|
|
1600
|
+
this._readable = new Readable({
|
|
1601
|
+
stream: this._stream.readable,
|
|
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);
|
|
1782
1615
|
}
|
|
1783
|
-
|
|
1784
|
-
|
|
1616
|
+
});
|
|
1617
|
+
this._writable.on("finish", () => this.emit("finish"));
|
|
1618
|
+
this._writable.on("drain", () => this.emit("drain"));
|
|
1619
|
+
// Only forward errors if not already errored (to prevent duplicate events)
|
|
1620
|
+
this._writable.on("error", err => {
|
|
1621
|
+
if (!this._errored) {
|
|
1622
|
+
this._errored = true;
|
|
1623
|
+
this.emit("error", err);
|
|
1785
1624
|
}
|
|
1786
|
-
}
|
|
1787
|
-
catch (err) {
|
|
1788
|
-
this._emitErrorOnce(err);
|
|
1789
|
-
throw err;
|
|
1790
|
-
}
|
|
1625
|
+
});
|
|
1791
1626
|
}
|
|
1792
1627
|
/**
|
|
1793
|
-
* Override on
|
|
1794
|
-
* Avoids starting flowing mode unless requested.
|
|
1628
|
+
* Override on to start flowing when data listener is added
|
|
1795
1629
|
*/
|
|
1796
1630
|
on(event, listener) {
|
|
1631
|
+
// Set up data forwarding when first external data listener is added
|
|
1797
1632
|
if (event === "data" && !this._dataForwardingSetup) {
|
|
1798
1633
|
this._dataForwardingSetup = true;
|
|
1799
|
-
this._readable.on("data",
|
|
1634
|
+
this._readable.on("data", data => this.emit("data", data));
|
|
1800
1635
|
}
|
|
1801
|
-
|
|
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();
|
|
1642
|
+
}
|
|
1643
|
+
return this;
|
|
1802
1644
|
}
|
|
1803
1645
|
write(chunk, encodingOrCallback, callback) {
|
|
1804
1646
|
const cb = typeof encodingOrCallback === "function" ? encodingOrCallback : callback;
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
this.
|
|
1647
|
+
if (this._destroyed || this._errored) {
|
|
1648
|
+
const err = new Error(this._errored ? "Cannot write after stream errored" : "Cannot write after stream destroyed");
|
|
1649
|
+
queueMicrotask(() => this.emit("error", err));
|
|
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;
|
|
1808
1665
|
}
|
|
1809
1666
|
return this._writable.write(chunk, cb);
|
|
1810
1667
|
}
|
|
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
|
+
}
|
|
1811
1694
|
end(chunkOrCallback, encodingOrCallback, callback) {
|
|
1812
1695
|
if (this._ended) {
|
|
1813
1696
|
return this;
|
|
1814
1697
|
}
|
|
1815
1698
|
this._ended = true;
|
|
1699
|
+
this._endPending = true;
|
|
1816
1700
|
const chunk = typeof chunkOrCallback === "function" ? undefined : chunkOrCallback;
|
|
1817
1701
|
const cb = typeof chunkOrCallback === "function"
|
|
1818
1702
|
? chunkOrCallback
|
|
@@ -1825,7 +1709,18 @@ class Transform extends event_emitter_1.EventEmitter {
|
|
|
1825
1709
|
if (chunk !== undefined) {
|
|
1826
1710
|
this._writable.write(chunk);
|
|
1827
1711
|
}
|
|
1828
|
-
|
|
1712
|
+
// Use setTimeout(0) instead of queueMicrotask to ensure all transform
|
|
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);
|
|
1829
1724
|
return this;
|
|
1830
1725
|
}
|
|
1831
1726
|
/**
|
|
@@ -1835,9 +1730,31 @@ class Transform extends event_emitter_1.EventEmitter {
|
|
|
1835
1730
|
return this._readable.read(size);
|
|
1836
1731
|
}
|
|
1837
1732
|
/**
|
|
1838
|
-
* Pipe
|
|
1733
|
+
* Pipe to another stream (writable, transform, or duplex)
|
|
1839
1734
|
*/
|
|
1840
1735
|
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
|
|
1841
1758
|
return this._readable.pipe(destination);
|
|
1842
1759
|
}
|
|
1843
1760
|
/**
|
|
@@ -1875,10 +1792,6 @@ class Transform extends event_emitter_1.EventEmitter {
|
|
|
1875
1792
|
return;
|
|
1876
1793
|
}
|
|
1877
1794
|
this._destroyed = true;
|
|
1878
|
-
if (this._sideForwardingCleanup) {
|
|
1879
|
-
this._sideForwardingCleanup();
|
|
1880
|
-
this._sideForwardingCleanup = null;
|
|
1881
|
-
}
|
|
1882
1795
|
this._readable.destroy(error);
|
|
1883
1796
|
this._writable.destroy(error);
|
|
1884
1797
|
queueMicrotask(() => this.emit("close"));
|
|
@@ -1887,44 +1800,7 @@ class Transform extends event_emitter_1.EventEmitter {
|
|
|
1887
1800
|
* Get the underlying Web TransformStream
|
|
1888
1801
|
*/
|
|
1889
1802
|
get webStream() {
|
|
1890
|
-
|
|
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;
|
|
1803
|
+
return this._stream;
|
|
1928
1804
|
}
|
|
1929
1805
|
get readable() {
|
|
1930
1806
|
return this._readable.readable;
|
|
@@ -1966,6 +1842,19 @@ class Transform extends event_emitter_1.EventEmitter {
|
|
|
1966
1842
|
* Async iterator support
|
|
1967
1843
|
*/
|
|
1968
1844
|
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
|
|
1969
1858
|
yield* this._readable[Symbol.asyncIterator]();
|
|
1970
1859
|
}
|
|
1971
1860
|
// =========================================================================
|
|
@@ -1976,18 +1865,23 @@ class Transform extends event_emitter_1.EventEmitter {
|
|
|
1976
1865
|
*/
|
|
1977
1866
|
static fromWeb(webStream, options) {
|
|
1978
1867
|
const transform = new Transform(options);
|
|
1979
|
-
|
|
1868
|
+
// Connect the web stream - set the internal _stream property
|
|
1869
|
+
transform._stream = webStream;
|
|
1980
1870
|
// Replace internal streams with the ones from the web stream
|
|
1981
1871
|
const newReadable = Readable.fromWeb(webStream.readable, { objectMode: options?.objectMode });
|
|
1982
1872
|
const newWritable = Writable.fromWeb(webStream.writable, { objectMode: options?.objectMode });
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
}
|
|
1873
|
+
// Remove old event listeners before replacing
|
|
1874
|
+
transform._readable.removeAllListeners();
|
|
1875
|
+
transform._writable.removeAllListeners();
|
|
1987
1876
|
transform._readable = newReadable;
|
|
1988
1877
|
transform._writable = newWritable;
|
|
1989
|
-
// Re-connect event forwarding
|
|
1990
|
-
transform.
|
|
1878
|
+
// Re-connect event forwarding
|
|
1879
|
+
newReadable.on("data", (data) => transform.emit("data", data));
|
|
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));
|
|
1991
1885
|
return transform;
|
|
1992
1886
|
}
|
|
1993
1887
|
/**
|
|
@@ -2044,13 +1938,7 @@ class Duplex extends event_emitter_1.EventEmitter {
|
|
|
2044
1938
|
callback();
|
|
2045
1939
|
}
|
|
2046
1940
|
});
|
|
2047
|
-
|
|
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 });
|
|
1941
|
+
readable.on("error", err => duplex.emit("error", err));
|
|
2054
1942
|
readable.pipe(sink);
|
|
2055
1943
|
};
|
|
2056
1944
|
// If it has readable and/or writable properties
|
|
@@ -2058,25 +1946,21 @@ class Duplex extends event_emitter_1.EventEmitter {
|
|
|
2058
1946
|
source !== null &&
|
|
2059
1947
|
"readable" in source &&
|
|
2060
1948
|
"writable" in source) {
|
|
1949
|
+
const duplex = new Duplex();
|
|
2061
1950
|
const pair = source;
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
1951
|
+
if (pair.readable) {
|
|
1952
|
+
forwardReadableToDuplex(pair.readable, duplex);
|
|
1953
|
+
}
|
|
1954
|
+
if (pair.writable) {
|
|
1955
|
+
return new Duplex({
|
|
1956
|
+
objectMode: duplex.writableObjectMode,
|
|
1957
|
+
write(chunk, encoding, callback) {
|
|
2069
1958
|
pair.writable.write(chunk, encoding, callback);
|
|
2070
|
-
}
|
|
2071
|
-
|
|
2072
|
-
final: pair.writable
|
|
2073
|
-
? callback => {
|
|
1959
|
+
},
|
|
1960
|
+
final(callback) {
|
|
2074
1961
|
pair.writable.end(callback);
|
|
2075
1962
|
}
|
|
2076
|
-
|
|
2077
|
-
});
|
|
2078
|
-
if (pair.readable) {
|
|
2079
|
-
forwardReadableToDuplex(pair.readable, duplex);
|
|
1963
|
+
});
|
|
2080
1964
|
}
|
|
2081
1965
|
return duplex;
|
|
2082
1966
|
}
|
|
@@ -2114,22 +1998,9 @@ class Duplex extends event_emitter_1.EventEmitter {
|
|
|
2114
1998
|
*/
|
|
2115
1999
|
static fromWeb(pair, options) {
|
|
2116
2000
|
const duplex = new Duplex(options);
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
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();
|
|
2001
|
+
// Replace internal streams
|
|
2002
|
+
duplex._readable = new Readable({ stream: pair.readable });
|
|
2003
|
+
duplex._writable = new Writable({ stream: pair.writable });
|
|
2133
2004
|
return duplex;
|
|
2134
2005
|
}
|
|
2135
2006
|
/**
|
|
@@ -2145,7 +2016,6 @@ class Duplex extends event_emitter_1.EventEmitter {
|
|
|
2145
2016
|
super();
|
|
2146
2017
|
// Track if we've already set up data forwarding
|
|
2147
2018
|
this._dataForwardingSetup = false;
|
|
2148
|
-
this._sideForwardingCleanup = null;
|
|
2149
2019
|
this.allowHalfOpen = options?.allowHalfOpen ?? true;
|
|
2150
2020
|
// Support shorthand objectMode option
|
|
2151
2021
|
const objectMode = options?.objectMode ?? false;
|
|
@@ -2162,31 +2032,23 @@ class Duplex extends event_emitter_1.EventEmitter {
|
|
|
2162
2032
|
write: options?.write?.bind(this),
|
|
2163
2033
|
final: options?.final?.bind(this)
|
|
2164
2034
|
});
|
|
2165
|
-
this._setupSideForwarding();
|
|
2166
|
-
}
|
|
2167
|
-
_setupSideForwarding() {
|
|
2168
|
-
if (this._sideForwardingCleanup) {
|
|
2169
|
-
this._sideForwardingCleanup();
|
|
2170
|
-
this._sideForwardingCleanup = null;
|
|
2171
|
-
}
|
|
2172
|
-
const registry = createListenerRegistry();
|
|
2173
2035
|
// Forward non-data events (data forwarding is lazy to avoid premature flowing)
|
|
2174
|
-
|
|
2036
|
+
this._readable.on("end", () => {
|
|
2175
2037
|
this.emit("end");
|
|
2038
|
+
// If not allowHalfOpen, end the writable side too
|
|
2176
2039
|
if (!this.allowHalfOpen) {
|
|
2177
2040
|
this._writable.end();
|
|
2178
2041
|
}
|
|
2179
2042
|
});
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2043
|
+
this._readable.on("error", err => this.emit("error", err));
|
|
2044
|
+
this._writable.on("finish", () => this.emit("finish"));
|
|
2045
|
+
this._writable.on("drain", () => this.emit("drain"));
|
|
2046
|
+
this._writable.on("close", () => {
|
|
2047
|
+
// If not allowHalfOpen, destroy the readable side too
|
|
2185
2048
|
if (!this.allowHalfOpen && !this._readable.destroyed) {
|
|
2186
2049
|
this._readable.destroy();
|
|
2187
2050
|
}
|
|
2188
2051
|
});
|
|
2189
|
-
this._sideForwardingCleanup = () => registry.cleanup();
|
|
2190
2052
|
}
|
|
2191
2053
|
/**
|
|
2192
2054
|
* Override on() to set up data forwarding lazily
|
|
@@ -2305,10 +2167,6 @@ class Duplex extends event_emitter_1.EventEmitter {
|
|
|
2305
2167
|
* Destroy both sides
|
|
2306
2168
|
*/
|
|
2307
2169
|
destroy(error) {
|
|
2308
|
-
if (this._sideForwardingCleanup) {
|
|
2309
|
-
this._sideForwardingCleanup();
|
|
2310
|
-
this._sideForwardingCleanup = null;
|
|
2311
|
-
}
|
|
2312
2170
|
this._readable.destroy(error);
|
|
2313
2171
|
this._writable.destroy(error);
|
|
2314
2172
|
return this;
|
|
@@ -2478,16 +2336,36 @@ exports.BufferedStream = BufferedStream;
|
|
|
2478
2336
|
* Create a readable stream with custom read implementation
|
|
2479
2337
|
*/
|
|
2480
2338
|
function createReadable(options) {
|
|
2481
|
-
|
|
2482
|
-
//
|
|
2483
|
-
|
|
2339
|
+
const readable = new Readable(options);
|
|
2340
|
+
// Override read behavior if provided
|
|
2341
|
+
if (options?.read) {
|
|
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;
|
|
2484
2349
|
}
|
|
2485
2350
|
/**
|
|
2486
2351
|
* Create a readable stream from an async iterable
|
|
2487
2352
|
*/
|
|
2488
2353
|
function createReadableFromAsyncIterable(iterable, options) {
|
|
2489
2354
|
const readable = new Readable({ ...options, objectMode: options?.objectMode ?? true });
|
|
2490
|
-
|
|
2355
|
+
(async () => {
|
|
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
|
+
})();
|
|
2491
2369
|
return readable;
|
|
2492
2370
|
}
|
|
2493
2371
|
/**
|
|
@@ -2516,8 +2394,38 @@ function createReadableFromArray(data, options) {
|
|
|
2516
2394
|
* Create a writable stream with custom write implementation
|
|
2517
2395
|
*/
|
|
2518
2396
|
function createWritable(options) {
|
|
2519
|
-
//
|
|
2520
|
-
|
|
2397
|
+
// Create a custom WritableStream with user's handlers
|
|
2398
|
+
const stream = new WritableStream({
|
|
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 });
|
|
2521
2429
|
}
|
|
2522
2430
|
/**
|
|
2523
2431
|
* Create a transform stream from a transform function
|
|
@@ -2652,17 +2560,11 @@ function pipeline(...args) {
|
|
|
2652
2560
|
const transforms = normalized.slice(1, -1);
|
|
2653
2561
|
let completed = false;
|
|
2654
2562
|
const allStreams = [source, ...transforms, destination];
|
|
2655
|
-
const
|
|
2656
|
-
let onAbort;
|
|
2657
|
-
const cleanupWithSignal = (error) => {
|
|
2563
|
+
const cleanup = (error) => {
|
|
2658
2564
|
if (completed) {
|
|
2659
2565
|
return;
|
|
2660
2566
|
}
|
|
2661
2567
|
completed = true;
|
|
2662
|
-
registry.cleanup();
|
|
2663
|
-
if (onAbort && options.signal) {
|
|
2664
|
-
options.signal.removeEventListener("abort", onAbort);
|
|
2665
|
-
}
|
|
2666
2568
|
// Destroy all streams on error
|
|
2667
2569
|
if (error) {
|
|
2668
2570
|
for (const stream of allStreams) {
|
|
@@ -2679,11 +2581,12 @@ function pipeline(...args) {
|
|
|
2679
2581
|
// Handle abort signal
|
|
2680
2582
|
if (options.signal) {
|
|
2681
2583
|
if (options.signal.aborted) {
|
|
2682
|
-
|
|
2584
|
+
cleanup(new Error("Pipeline aborted"));
|
|
2683
2585
|
return;
|
|
2684
2586
|
}
|
|
2685
|
-
|
|
2686
|
-
|
|
2587
|
+
options.signal.addEventListener("abort", () => {
|
|
2588
|
+
cleanup(new Error("Pipeline aborted"));
|
|
2589
|
+
});
|
|
2687
2590
|
}
|
|
2688
2591
|
// Chain the streams
|
|
2689
2592
|
let current = source;
|
|
@@ -2697,35 +2600,13 @@ function pipeline(...args) {
|
|
|
2697
2600
|
}
|
|
2698
2601
|
else {
|
|
2699
2602
|
// Don't end destination
|
|
2700
|
-
|
|
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);
|
|
2603
|
+
current.on("data", chunk => destination.write(chunk));
|
|
2723
2604
|
}
|
|
2724
2605
|
// Handle completion
|
|
2725
|
-
|
|
2606
|
+
destination.on("finish", () => cleanup());
|
|
2726
2607
|
// Handle errors on all streams
|
|
2727
2608
|
for (const stream of allStreams) {
|
|
2728
|
-
|
|
2609
|
+
stream.on("error", (err) => cleanup(err));
|
|
2729
2610
|
}
|
|
2730
2611
|
});
|
|
2731
2612
|
// If callback provided, use it
|
|
@@ -2765,20 +2646,11 @@ function finished(stream, optionsOrCallback, callback) {
|
|
|
2765
2646
|
const promise = new Promise((resolve, reject) => {
|
|
2766
2647
|
const normalizedStream = toBrowserPipelineStream(stream);
|
|
2767
2648
|
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
|
-
};
|
|
2776
2649
|
const done = (err) => {
|
|
2777
2650
|
if (resolved) {
|
|
2778
2651
|
return;
|
|
2779
2652
|
}
|
|
2780
2653
|
resolved = true;
|
|
2781
|
-
cleanup();
|
|
2782
2654
|
if (err && !options.error) {
|
|
2783
2655
|
reject(err);
|
|
2784
2656
|
}
|
|
@@ -2792,8 +2664,9 @@ function finished(stream, optionsOrCallback, callback) {
|
|
|
2792
2664
|
done(new Error("Aborted"));
|
|
2793
2665
|
return;
|
|
2794
2666
|
}
|
|
2795
|
-
|
|
2796
|
-
|
|
2667
|
+
options.signal.addEventListener("abort", () => {
|
|
2668
|
+
done(new Error("Aborted"));
|
|
2669
|
+
});
|
|
2797
2670
|
}
|
|
2798
2671
|
const checkReadable = options.readable !== false;
|
|
2799
2672
|
const checkWritable = options.writable !== false;
|
|
@@ -2808,13 +2681,13 @@ function finished(stream, optionsOrCallback, callback) {
|
|
|
2808
2681
|
}
|
|
2809
2682
|
// Listen for events
|
|
2810
2683
|
if (checkWritable) {
|
|
2811
|
-
|
|
2684
|
+
normalizedStream.on("finish", () => done());
|
|
2812
2685
|
}
|
|
2813
2686
|
if (checkReadable) {
|
|
2814
|
-
|
|
2687
|
+
normalizedStream.on("end", () => done());
|
|
2815
2688
|
}
|
|
2816
|
-
|
|
2817
|
-
|
|
2689
|
+
normalizedStream.on("error", (err) => done(err));
|
|
2690
|
+
normalizedStream.on("close", () => done());
|
|
2818
2691
|
});
|
|
2819
2692
|
// If callback provided, use it
|
|
2820
2693
|
if (cb) {
|
|
@@ -2836,8 +2709,38 @@ async function streamToPromise(stream) {
|
|
|
2836
2709
|
* (Browser equivalent of Node.js streamToBuffer)
|
|
2837
2710
|
*/
|
|
2838
2711
|
async function streamToUint8Array(stream) {
|
|
2839
|
-
|
|
2840
|
-
|
|
2712
|
+
let iterable;
|
|
2713
|
+
if (isReadableStream(stream)) {
|
|
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;
|
|
2841
2744
|
}
|
|
2842
2745
|
/**
|
|
2843
2746
|
* Alias for streamToUint8Array (Node.js compatibility)
|
|
@@ -2847,10 +2750,8 @@ exports.streamToBuffer = streamToUint8Array;
|
|
|
2847
2750
|
* Collect all data from a readable stream into a string
|
|
2848
2751
|
*/
|
|
2849
2752
|
async function streamToString(stream, encoding) {
|
|
2850
|
-
const
|
|
2851
|
-
|
|
2852
|
-
const decoder = encoding ? (0, shared_1.getTextDecoder)(encoding) : shared_1.textDecoder;
|
|
2853
|
-
return decoder.decode(combined);
|
|
2753
|
+
const buffer = await streamToUint8Array(stream);
|
|
2754
|
+
return (0, shared_1.getTextDecoder)(encoding).decode(buffer);
|
|
2854
2755
|
}
|
|
2855
2756
|
/**
|
|
2856
2757
|
* Drain a stream (consume all data without processing)
|
|
@@ -2938,19 +2839,14 @@ function addAbortSignal(signal, stream) {
|
|
|
2938
2839
|
stream.destroy(new Error("Aborted"));
|
|
2939
2840
|
return stream;
|
|
2940
2841
|
}
|
|
2941
|
-
const cleanup = () => {
|
|
2942
|
-
signal.removeEventListener("abort", onAbort);
|
|
2943
|
-
removeEmitterListener(stream, "close", onClose);
|
|
2944
|
-
};
|
|
2945
2842
|
const onAbort = () => {
|
|
2946
|
-
cleanup();
|
|
2947
2843
|
stream.destroy(new Error("Aborted"));
|
|
2948
2844
|
};
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2845
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2846
|
+
// Clean up when stream is destroyed
|
|
2847
|
+
stream.on("close", () => {
|
|
2848
|
+
signal.removeEventListener("abort", onAbort);
|
|
2849
|
+
});
|
|
2954
2850
|
return stream;
|
|
2955
2851
|
}
|
|
2956
2852
|
/**
|
|
@@ -2959,68 +2855,60 @@ function addAbortSignal(signal, stream) {
|
|
|
2959
2855
|
function createDuplex(options) {
|
|
2960
2856
|
const readableObjectMode = options?.readableObjectMode ?? options?.objectMode;
|
|
2961
2857
|
const writableObjectMode = options?.writableObjectMode ?? options?.objectMode;
|
|
2962
|
-
const underlyingWritable = options?.writable;
|
|
2963
2858
|
const duplex = new Duplex({
|
|
2964
2859
|
allowHalfOpen: options?.allowHalfOpen,
|
|
2965
2860
|
readableHighWaterMark: options?.readableHighWaterMark,
|
|
2966
2861
|
writableHighWaterMark: options?.writableHighWaterMark,
|
|
2967
2862
|
readableObjectMode,
|
|
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)
|
|
2863
|
+
writableObjectMode
|
|
2999
2864
|
});
|
|
3000
|
-
// If
|
|
2865
|
+
// If custom readable/writable provided, pipe them
|
|
3001
2866
|
if (options?.readable) {
|
|
3002
2867
|
const readable = options.readable;
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
};
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
2868
|
+
readable.on?.("data", (chunk) => duplex.push(chunk));
|
|
2869
|
+
readable.on?.("end", () => duplex.push(null));
|
|
2870
|
+
readable.on?.("error", (err) => duplex.destroy(err));
|
|
2871
|
+
}
|
|
2872
|
+
if (options?.writable) {
|
|
2873
|
+
const writable = options.writable;
|
|
2874
|
+
duplex.on("data", (chunk) => writable.write?.(chunk));
|
|
2875
|
+
duplex.on("finish", () => writable.end?.());
|
|
2876
|
+
}
|
|
2877
|
+
// If custom read/write/final provided, override methods
|
|
2878
|
+
if (options?.write) {
|
|
2879
|
+
const _originalWrite = duplex.write.bind(duplex); // Keep bound reference for potential future use
|
|
2880
|
+
duplex.write = function (chunk, encodingOrCallback, callback) {
|
|
2881
|
+
const encoding = typeof encodingOrCallback === "string" ? encodingOrCallback : "utf8";
|
|
2882
|
+
const cb = typeof encodingOrCallback === "function" ? encodingOrCallback : (callback ?? (() => { }));
|
|
2883
|
+
options.write.call(duplex, chunk, encoding, cb);
|
|
2884
|
+
return true;
|
|
2885
|
+
};
|
|
2886
|
+
}
|
|
2887
|
+
if (options?.final) {
|
|
2888
|
+
const originalEnd = duplex.end.bind(duplex);
|
|
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
|
+
};
|
|
3024
2912
|
}
|
|
3025
2913
|
if (options?.destroy) {
|
|
3026
2914
|
const originalDestroy = duplex.destroy.bind(duplex);
|
|
@@ -3044,7 +2932,20 @@ function createDuplex(options) {
|
|
|
3044
2932
|
*/
|
|
3045
2933
|
function createReadableFromGenerator(generator, options) {
|
|
3046
2934
|
const readable = new Readable({ ...options, objectMode: options?.objectMode ?? true });
|
|
3047
|
-
|
|
2935
|
+
(async () => {
|
|
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
|
+
})();
|
|
3048
2949
|
return readable;
|
|
3049
2950
|
}
|
|
3050
2951
|
/**
|
|
@@ -3074,8 +2975,8 @@ function compose(...transforms) {
|
|
|
3074
2975
|
transform: chunk => chunk
|
|
3075
2976
|
});
|
|
3076
2977
|
}
|
|
3077
|
-
|
|
3078
|
-
if (len === 1) {
|
|
2978
|
+
const isNativeTransform = (stream) => stream instanceof Transform;
|
|
2979
|
+
if (len === 1 && isNativeTransform(transforms[0])) {
|
|
3079
2980
|
return transforms[0];
|
|
3080
2981
|
}
|
|
3081
2982
|
// Chain the transforms: first → second → ... → last
|
|
@@ -3086,34 +2987,19 @@ function compose(...transforms) {
|
|
|
3086
2987
|
transforms[i].pipe(transforms[i + 1]);
|
|
3087
2988
|
}
|
|
3088
2989
|
class ComposedTransform extends Transform {
|
|
3089
|
-
constructor(
|
|
3090
|
-
super(
|
|
2990
|
+
constructor() {
|
|
2991
|
+
super(...arguments);
|
|
3091
2992
|
this._dataForwarding = false;
|
|
3092
2993
|
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
|
-
}
|
|
3102
2994
|
}
|
|
3103
2995
|
on(event, listener) {
|
|
3104
2996
|
if (event === "data" && !this._dataForwarding) {
|
|
3105
2997
|
this._dataForwarding = true;
|
|
3106
|
-
|
|
3107
|
-
this.emit("data", chunk);
|
|
3108
|
-
};
|
|
3109
|
-
this._dataForwardCleanup = addEmitterListener(last, "data", onData);
|
|
2998
|
+
last.on("data", (chunk) => this.emit("data", chunk));
|
|
3110
2999
|
}
|
|
3111
3000
|
if (event === "end" && !this._endForwarding) {
|
|
3112
3001
|
this._endForwarding = true;
|
|
3113
|
-
|
|
3114
|
-
this.emit("end");
|
|
3115
|
-
};
|
|
3116
|
-
this._endForwardCleanup = addEmitterListener(last, "end", onEnd, { once: true });
|
|
3002
|
+
last.on("end", () => this.emit("end"));
|
|
3117
3003
|
}
|
|
3118
3004
|
return super.on(event, listener);
|
|
3119
3005
|
}
|
|
@@ -3139,18 +3025,6 @@ function compose(...transforms) {
|
|
|
3139
3025
|
return last.pipe(destination);
|
|
3140
3026
|
}
|
|
3141
3027
|
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;
|
|
3154
3028
|
for (const t of transforms) {
|
|
3155
3029
|
t.destroy(error);
|
|
3156
3030
|
}
|
|
@@ -3174,6 +3048,12 @@ function compose(...transforms) {
|
|
|
3174
3048
|
objectMode: first?.objectMode ?? true,
|
|
3175
3049
|
transform: chunk => chunk
|
|
3176
3050
|
});
|
|
3051
|
+
// Forward errors from any transform
|
|
3052
|
+
for (const t of transforms) {
|
|
3053
|
+
t.on("error", (err) => {
|
|
3054
|
+
composed.emit("error", err);
|
|
3055
|
+
});
|
|
3056
|
+
}
|
|
3177
3057
|
// Reflect underlying readability/writability like the previous duck-typed wrapper
|
|
3178
3058
|
Object.defineProperty(composed, "readable", {
|
|
3179
3059
|
get: () => last.readable
|