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