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