@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.
Files changed (232) hide show
  1. package/THIRD_PARTY_NOTICES.md +31 -0
  2. package/dist/browser/index.browser.d.ts +0 -1
  3. package/dist/browser/index.browser.js +0 -12
  4. package/dist/browser/modules/archive/byte-queue.d.ts +18 -0
  5. package/dist/browser/modules/archive/byte-queue.js +125 -0
  6. package/dist/browser/modules/archive/{compression/compress.base.js → compress.base.js} +1 -1
  7. package/dist/browser/modules/archive/{compression/compress.browser.d.ts → compress.browser.d.ts} +8 -2
  8. package/dist/{esm/modules/archive/compression → browser/modules/archive}/compress.browser.js +11 -3
  9. package/dist/browser/modules/archive/{compression/compress.d.ts → compress.d.ts} +2 -2
  10. package/dist/{esm/modules/archive/compression → browser/modules/archive}/compress.js +1 -1
  11. package/dist/browser/modules/archive/{compression/crc32.browser.d.ts → crc32.browser.d.ts} +1 -1
  12. package/dist/browser/modules/archive/{compression/crc32.d.ts → crc32.d.ts} +1 -1
  13. package/dist/browser/modules/archive/{compression/crc32.js → crc32.js} +1 -1
  14. package/dist/browser/modules/archive/defaults.d.ts +0 -1
  15. package/dist/browser/modules/archive/defaults.js +3 -6
  16. package/dist/browser/modules/archive/{compression/deflate-fallback.js → deflate-fallback.js} +1 -1
  17. package/dist/browser/modules/archive/{unzip/extract.d.ts → extract.d.ts} +2 -2
  18. package/dist/browser/modules/archive/index.base.d.ts +4 -4
  19. package/dist/browser/modules/archive/index.base.js +6 -3
  20. package/dist/browser/modules/archive/index.browser.d.ts +4 -3
  21. package/dist/browser/modules/archive/index.browser.js +7 -3
  22. package/dist/browser/modules/archive/index.d.ts +4 -3
  23. package/dist/browser/modules/archive/index.js +5 -3
  24. package/dist/browser/modules/archive/{unzip/stream.base.d.ts → parse.base.d.ts} +2 -36
  25. package/dist/browser/modules/archive/parse.base.js +644 -0
  26. package/dist/browser/modules/archive/{unzip/stream.browser.d.ts → parse.browser.d.ts} +1 -1
  27. package/dist/{esm/modules/archive/unzip/stream.browser.js → browser/modules/archive/parse.browser.js} +110 -371
  28. package/dist/browser/modules/archive/{unzip/stream.d.ts → parse.d.ts} +2 -2
  29. package/dist/{esm/modules/archive/unzip/stream.js → browser/modules/archive/parse.js} +5 -6
  30. package/dist/browser/modules/archive/{compression/streaming-compress.browser.d.ts → streaming-compress.browser.d.ts} +2 -2
  31. package/dist/browser/modules/archive/{compression/streaming-compress.browser.js → streaming-compress.browser.js} +3 -3
  32. package/dist/browser/modules/archive/{compression/streaming-compress.d.ts → streaming-compress.d.ts} +2 -2
  33. package/dist/browser/modules/archive/{compression/streaming-compress.js → streaming-compress.js} +2 -2
  34. package/dist/browser/modules/archive/{zip/stream.d.ts → streaming-zip.d.ts} +5 -28
  35. package/dist/{esm/modules/archive/zip/stream.js → browser/modules/archive/streaming-zip.js} +48 -192
  36. package/dist/browser/modules/archive/utils/bytes.js +16 -16
  37. package/dist/browser/modules/archive/utils/parse-buffer.js +23 -21
  38. package/dist/browser/modules/archive/utils/timestamps.js +1 -62
  39. package/dist/browser/modules/archive/utils/zip-extra-fields.d.ts +1 -1
  40. package/dist/browser/modules/archive/utils/zip-extra-fields.js +14 -26
  41. package/dist/browser/modules/archive/utils/zip-extra.d.ts +18 -0
  42. package/dist/browser/modules/archive/utils/zip-extra.js +68 -0
  43. package/dist/browser/modules/archive/zip-builder.d.ts +117 -0
  44. package/dist/browser/modules/archive/zip-builder.js +292 -0
  45. package/dist/browser/modules/archive/zip-constants.d.ts +18 -0
  46. package/dist/browser/modules/archive/zip-constants.js +23 -0
  47. package/dist/{esm/modules/archive/zip → browser/modules/archive}/zip-entry-metadata.js +3 -3
  48. package/dist/{types/modules/archive/unzip → browser/modules/archive}/zip-parser.d.ts +1 -1
  49. package/dist/{esm/modules/archive/unzip → browser/modules/archive}/zip-parser.js +24 -38
  50. package/dist/browser/modules/archive/{zip-spec/zip-records.d.ts → zip-records.d.ts} +0 -20
  51. package/dist/browser/modules/archive/zip-records.js +84 -0
  52. package/dist/browser/modules/excel/stream/workbook-reader.browser.js +1 -1
  53. package/dist/browser/modules/excel/stream/workbook-writer.browser.d.ts +1 -1
  54. package/dist/browser/modules/excel/stream/workbook-writer.browser.js +1 -1
  55. package/dist/browser/modules/excel/xlsx/xlsx.browser.js +6 -3
  56. package/dist/browser/modules/excel/xlsx/xlsx.js +1 -1
  57. package/dist/browser/modules/stream/streams.browser.d.ts +30 -28
  58. package/dist/browser/modules/stream/streams.browser.js +710 -830
  59. package/dist/browser/modules/stream/streams.js +58 -140
  60. package/dist/cjs/modules/archive/byte-queue.js +129 -0
  61. package/dist/cjs/modules/archive/{compression/compress.base.js → compress.base.js} +1 -1
  62. package/dist/cjs/modules/archive/{compression/compress.browser.js → compress.browser.js} +11 -3
  63. package/dist/cjs/modules/archive/{compression/compress.js → compress.js} +1 -1
  64. package/dist/cjs/modules/archive/{compression/crc32.js → crc32.js} +1 -1
  65. package/dist/cjs/modules/archive/defaults.js +4 -7
  66. package/dist/cjs/modules/archive/{compression/deflate-fallback.js → deflate-fallback.js} +1 -1
  67. package/dist/cjs/modules/archive/index.base.js +19 -9
  68. package/dist/cjs/modules/archive/index.browser.js +10 -4
  69. package/dist/cjs/modules/archive/index.js +8 -4
  70. package/dist/cjs/modules/archive/parse.base.js +666 -0
  71. package/dist/cjs/modules/archive/{unzip/stream.browser.js → parse.browser.js} +111 -372
  72. package/dist/cjs/modules/archive/{unzip/stream.js → parse.js} +8 -9
  73. package/dist/cjs/modules/archive/{compression/streaming-compress.browser.js → streaming-compress.browser.js} +3 -3
  74. package/dist/cjs/modules/archive/{compression/streaming-compress.js → streaming-compress.js} +2 -2
  75. package/dist/cjs/modules/archive/{zip/stream.js → streaming-zip.js} +50 -194
  76. package/dist/cjs/modules/archive/utils/bytes.js +16 -16
  77. package/dist/cjs/modules/archive/utils/parse-buffer.js +23 -21
  78. package/dist/cjs/modules/archive/utils/timestamps.js +3 -64
  79. package/dist/cjs/modules/archive/utils/zip-extra-fields.js +14 -26
  80. package/dist/cjs/modules/archive/utils/zip-extra.js +74 -0
  81. package/dist/cjs/modules/archive/zip-builder.js +297 -0
  82. package/dist/cjs/modules/archive/zip-constants.js +26 -0
  83. package/dist/cjs/modules/archive/{zip/zip-entry-metadata.js → zip-entry-metadata.js} +5 -5
  84. package/dist/cjs/modules/archive/{unzip/zip-parser.js → zip-parser.js} +33 -47
  85. package/dist/cjs/modules/archive/zip-records.js +90 -0
  86. package/dist/cjs/modules/excel/stream/workbook-reader.browser.js +2 -2
  87. package/dist/cjs/modules/excel/stream/workbook-writer.browser.js +4 -4
  88. package/dist/cjs/modules/excel/xlsx/xlsx.browser.js +9 -6
  89. package/dist/cjs/modules/excel/xlsx/xlsx.js +2 -2
  90. package/dist/cjs/modules/stream/streams.browser.js +710 -830
  91. package/dist/cjs/modules/stream/streams.js +58 -140
  92. package/dist/esm/index.browser.js +0 -12
  93. package/dist/esm/modules/archive/byte-queue.js +125 -0
  94. package/dist/esm/modules/archive/{compression/compress.base.js → compress.base.js} +1 -1
  95. package/dist/{browser/modules/archive/compression → esm/modules/archive}/compress.browser.js +11 -3
  96. package/dist/{browser/modules/archive/compression → esm/modules/archive}/compress.js +1 -1
  97. package/dist/esm/modules/archive/{compression/crc32.js → crc32.js} +1 -1
  98. package/dist/esm/modules/archive/defaults.js +3 -6
  99. package/dist/esm/modules/archive/{compression/deflate-fallback.js → deflate-fallback.js} +1 -1
  100. package/dist/esm/modules/archive/index.base.js +6 -3
  101. package/dist/esm/modules/archive/index.browser.js +7 -3
  102. package/dist/esm/modules/archive/index.js +5 -3
  103. package/dist/esm/modules/archive/parse.base.js +644 -0
  104. package/dist/{browser/modules/archive/unzip/stream.browser.js → esm/modules/archive/parse.browser.js} +110 -371
  105. package/dist/{browser/modules/archive/unzip/stream.js → esm/modules/archive/parse.js} +5 -6
  106. package/dist/esm/modules/archive/{compression/streaming-compress.browser.js → streaming-compress.browser.js} +3 -3
  107. package/dist/esm/modules/archive/{compression/streaming-compress.js → streaming-compress.js} +2 -2
  108. package/dist/{browser/modules/archive/zip/stream.js → esm/modules/archive/streaming-zip.js} +48 -192
  109. package/dist/esm/modules/archive/utils/bytes.js +16 -16
  110. package/dist/esm/modules/archive/utils/parse-buffer.js +23 -21
  111. package/dist/esm/modules/archive/utils/timestamps.js +1 -62
  112. package/dist/esm/modules/archive/utils/zip-extra-fields.js +14 -26
  113. package/dist/esm/modules/archive/utils/zip-extra.js +68 -0
  114. package/dist/esm/modules/archive/zip-builder.js +292 -0
  115. package/dist/esm/modules/archive/zip-constants.js +23 -0
  116. package/dist/{browser/modules/archive/zip → esm/modules/archive}/zip-entry-metadata.js +3 -3
  117. package/dist/{browser/modules/archive/unzip → esm/modules/archive}/zip-parser.js +24 -38
  118. package/dist/esm/modules/archive/zip-records.js +84 -0
  119. package/dist/esm/modules/excel/stream/workbook-reader.browser.js +1 -1
  120. package/dist/esm/modules/excel/stream/workbook-writer.browser.js +1 -1
  121. package/dist/esm/modules/excel/xlsx/xlsx.browser.js +6 -3
  122. package/dist/esm/modules/excel/xlsx/xlsx.js +1 -1
  123. package/dist/esm/modules/stream/streams.browser.js +710 -830
  124. package/dist/esm/modules/stream/streams.js +58 -140
  125. package/dist/iife/THIRD_PARTY_NOTICES.md +31 -0
  126. package/dist/iife/excelts.iife.js +4425 -6215
  127. package/dist/iife/excelts.iife.js.map +1 -1
  128. package/dist/iife/excelts.iife.min.js +31 -103
  129. package/dist/types/index.browser.d.ts +0 -1
  130. package/dist/types/modules/archive/byte-queue.d.ts +18 -0
  131. package/dist/types/modules/archive/{compression/compress.browser.d.ts → compress.browser.d.ts} +8 -2
  132. package/dist/types/modules/archive/defaults.d.ts +0 -1
  133. package/dist/types/modules/archive/index.base.d.ts +4 -4
  134. package/dist/types/modules/archive/index.browser.d.ts +4 -3
  135. package/dist/types/modules/archive/index.d.ts +4 -3
  136. package/dist/types/modules/archive/{unzip/stream.base.d.ts → parse.base.d.ts} +4 -38
  137. package/dist/types/modules/archive/{unzip/stream.browser.d.ts → parse.browser.d.ts} +2 -2
  138. package/dist/types/modules/archive/{unzip/stream.d.ts → parse.d.ts} +3 -3
  139. package/dist/types/modules/archive/{compression/streaming-compress.browser.d.ts → streaming-compress.browser.d.ts} +1 -1
  140. package/dist/types/modules/archive/{zip/stream.d.ts → streaming-zip.d.ts} +6 -29
  141. package/dist/types/modules/archive/utils/zip-extra-fields.d.ts +1 -1
  142. package/dist/types/modules/archive/utils/zip-extra.d.ts +18 -0
  143. package/dist/types/modules/archive/zip-builder.d.ts +117 -0
  144. package/dist/types/modules/archive/zip-constants.d.ts +18 -0
  145. package/dist/types/modules/archive/{zip/zip-entry-metadata.d.ts → zip-entry-metadata.d.ts} +1 -1
  146. package/dist/{browser/modules/archive/unzip → types/modules/archive}/zip-parser.d.ts +1 -1
  147. package/dist/types/modules/archive/{zip-spec/zip-records.d.ts → zip-records.d.ts} +0 -20
  148. package/dist/types/modules/excel/stream/workbook-writer.browser.d.ts +1 -1
  149. package/dist/types/modules/stream/streams.browser.d.ts +30 -28
  150. package/package.json +1 -5
  151. package/dist/browser/modules/archive/internal/byte-queue.d.ts +0 -33
  152. package/dist/browser/modules/archive/internal/byte-queue.js +0 -407
  153. package/dist/browser/modules/archive/io/archive-sink.d.ts +0 -9
  154. package/dist/browser/modules/archive/io/archive-sink.js +0 -77
  155. package/dist/browser/modules/archive/io/archive-source.d.ts +0 -8
  156. package/dist/browser/modules/archive/io/archive-source.js +0 -107
  157. package/dist/browser/modules/archive/unzip/index.d.ts +0 -40
  158. package/dist/browser/modules/archive/unzip/index.js +0 -164
  159. package/dist/browser/modules/archive/unzip/stream.base.js +0 -1022
  160. package/dist/browser/modules/archive/utils/async-queue.d.ts +0 -7
  161. package/dist/browser/modules/archive/utils/async-queue.js +0 -103
  162. package/dist/browser/modules/archive/utils/compressibility.d.ts +0 -10
  163. package/dist/browser/modules/archive/utils/compressibility.js +0 -57
  164. package/dist/browser/modules/archive/utils/pattern-scanner.d.ts +0 -21
  165. package/dist/browser/modules/archive/utils/pattern-scanner.js +0 -27
  166. package/dist/browser/modules/archive/zip/index.d.ts +0 -42
  167. package/dist/browser/modules/archive/zip/index.js +0 -157
  168. package/dist/browser/modules/archive/zip/zip-bytes.d.ts +0 -73
  169. package/dist/browser/modules/archive/zip/zip-bytes.js +0 -239
  170. package/dist/browser/modules/archive/zip-spec/zip-records.js +0 -126
  171. package/dist/cjs/modules/archive/internal/byte-queue.js +0 -411
  172. package/dist/cjs/modules/archive/io/archive-sink.js +0 -82
  173. package/dist/cjs/modules/archive/io/archive-source.js +0 -114
  174. package/dist/cjs/modules/archive/unzip/index.js +0 -170
  175. package/dist/cjs/modules/archive/unzip/stream.base.js +0 -1044
  176. package/dist/cjs/modules/archive/utils/async-queue.js +0 -106
  177. package/dist/cjs/modules/archive/utils/compressibility.js +0 -60
  178. package/dist/cjs/modules/archive/utils/pattern-scanner.js +0 -31
  179. package/dist/cjs/modules/archive/zip/index.js +0 -162
  180. package/dist/cjs/modules/archive/zip/zip-bytes.js +0 -242
  181. package/dist/cjs/modules/archive/zip-spec/zip-records.js +0 -136
  182. package/dist/esm/modules/archive/internal/byte-queue.js +0 -407
  183. package/dist/esm/modules/archive/io/archive-sink.js +0 -77
  184. package/dist/esm/modules/archive/io/archive-source.js +0 -107
  185. package/dist/esm/modules/archive/unzip/index.js +0 -164
  186. package/dist/esm/modules/archive/unzip/stream.base.js +0 -1022
  187. package/dist/esm/modules/archive/utils/async-queue.js +0 -103
  188. package/dist/esm/modules/archive/utils/compressibility.js +0 -57
  189. package/dist/esm/modules/archive/utils/pattern-scanner.js +0 -27
  190. package/dist/esm/modules/archive/zip/index.js +0 -157
  191. package/dist/esm/modules/archive/zip/zip-bytes.js +0 -239
  192. package/dist/esm/modules/archive/zip-spec/zip-records.js +0 -126
  193. package/dist/types/modules/archive/internal/byte-queue.d.ts +0 -33
  194. package/dist/types/modules/archive/io/archive-sink.d.ts +0 -9
  195. package/dist/types/modules/archive/io/archive-source.d.ts +0 -8
  196. package/dist/types/modules/archive/unzip/index.d.ts +0 -40
  197. package/dist/types/modules/archive/utils/async-queue.d.ts +0 -7
  198. package/dist/types/modules/archive/utils/compressibility.d.ts +0 -10
  199. package/dist/types/modules/archive/utils/pattern-scanner.d.ts +0 -21
  200. package/dist/types/modules/archive/zip/index.d.ts +0 -42
  201. package/dist/types/modules/archive/zip/zip-bytes.d.ts +0 -73
  202. /package/dist/browser/modules/archive/{compression/compress.base.d.ts → compress.base.d.ts} +0 -0
  203. /package/dist/browser/modules/archive/{compression/crc32.base.d.ts → crc32.base.d.ts} +0 -0
  204. /package/dist/browser/modules/archive/{compression/crc32.base.js → crc32.base.js} +0 -0
  205. /package/dist/browser/modules/archive/{compression/crc32.browser.js → crc32.browser.js} +0 -0
  206. /package/dist/browser/modules/archive/{compression/deflate-fallback.d.ts → deflate-fallback.d.ts} +0 -0
  207. /package/dist/browser/modules/archive/{unzip/extract.js → extract.js} +0 -0
  208. /package/dist/browser/modules/archive/{compression/streaming-compress.base.d.ts → streaming-compress.base.d.ts} +0 -0
  209. /package/dist/browser/modules/archive/{compression/streaming-compress.base.js → streaming-compress.base.js} +0 -0
  210. /package/dist/browser/modules/archive/{zip-spec/zip-entry-info.d.ts → zip-entry-info.d.ts} +0 -0
  211. /package/dist/browser/modules/archive/{zip-spec/zip-entry-info.js → zip-entry-info.js} +0 -0
  212. /package/dist/browser/modules/archive/{zip/zip-entry-metadata.d.ts → zip-entry-metadata.d.ts} +0 -0
  213. /package/dist/cjs/modules/archive/{compression/crc32.base.js → crc32.base.js} +0 -0
  214. /package/dist/cjs/modules/archive/{compression/crc32.browser.js → crc32.browser.js} +0 -0
  215. /package/dist/cjs/modules/archive/{unzip/extract.js → extract.js} +0 -0
  216. /package/dist/cjs/modules/archive/{compression/streaming-compress.base.js → streaming-compress.base.js} +0 -0
  217. /package/dist/cjs/modules/archive/{zip-spec/zip-entry-info.js → zip-entry-info.js} +0 -0
  218. /package/dist/esm/modules/archive/{compression/crc32.base.js → crc32.base.js} +0 -0
  219. /package/dist/esm/modules/archive/{compression/crc32.browser.js → crc32.browser.js} +0 -0
  220. /package/dist/esm/modules/archive/{unzip/extract.js → extract.js} +0 -0
  221. /package/dist/esm/modules/archive/{compression/streaming-compress.base.js → streaming-compress.base.js} +0 -0
  222. /package/dist/esm/modules/archive/{zip-spec/zip-entry-info.js → zip-entry-info.js} +0 -0
  223. /package/dist/types/modules/archive/{compression/compress.base.d.ts → compress.base.d.ts} +0 -0
  224. /package/dist/types/modules/archive/{compression/compress.d.ts → compress.d.ts} +0 -0
  225. /package/dist/types/modules/archive/{compression/crc32.base.d.ts → crc32.base.d.ts} +0 -0
  226. /package/dist/types/modules/archive/{compression/crc32.browser.d.ts → crc32.browser.d.ts} +0 -0
  227. /package/dist/types/modules/archive/{compression/crc32.d.ts → crc32.d.ts} +0 -0
  228. /package/dist/types/modules/archive/{compression/deflate-fallback.d.ts → deflate-fallback.d.ts} +0 -0
  229. /package/dist/types/modules/archive/{unzip/extract.d.ts → extract.d.ts} +0 -0
  230. /package/dist/types/modules/archive/{compression/streaming-compress.base.d.ts → streaming-compress.base.d.ts} +0 -0
  231. /package/dist/types/modules/archive/{compression/streaming-compress.d.ts → streaming-compress.d.ts} +0 -0
  232. /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
- pumpAsyncIterableToReadable(readable, toAsyncIterable(iterable));
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
- // Emit 'end' only after buffered data is fully drained.
170
- // This avoids premature 'end' when producers push null while paused.
171
- if (this._bufferedLength() === 0) {
172
- this._emitEndOnce();
173
- }
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", this._applyEncoding(chunk));
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: We still buffer for Node-style read()/data semantics.
218
- // The internal Web ReadableStream is also fed via controller.enqueue() above.
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
- const decoded = this._applyEncoding(chunk);
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
- const decoded = this._applyEncoding(chunk);
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", this._applyEncoding(chunk));
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._emitEndOnce();
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
- // Install a removable, once-style drain listener.
471
- if (!drainListener) {
472
- drainListener = () => {
473
- removeDrainListener();
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", drainListener);
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
- const reader = this._reader;
577
- this._reader = null;
578
- reader
579
- .cancel()
580
- .catch(() => { })
581
- .finally(() => {
582
- try {
583
- reader.releaseLock();
584
- }
585
- catch {
586
- // Ignore if a read is still pending
587
- }
588
- });
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._emitEndOnce();
682
- if (this._reader) {
683
- const reader = this._reader;
684
- this._reader = null;
685
- try {
686
- reader.releaseLock();
687
- }
688
- catch {
689
- // Ignore if a read is still pending
690
- }
691
- }
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", this._applyEncoding(value));
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 a unified event-queue iterator with simple backpressure.
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 this._applyEncoding(chunk);
617
+ yield chunk;
739
618
  }
619
+ // If already ended, we're done
740
620
  if (this._ended) {
741
621
  return;
742
622
  }
743
- const highWaterMark = this.readableHighWaterMark;
744
- const lowWaterMark = Math.max(0, Math.floor(highWaterMark / 2));
745
- const chunkSizeForBackpressure = (chunk) => {
746
- if (this.objectMode) {
747
- return 1;
748
- }
749
- if (chunk instanceof Uint8Array) {
750
- return chunk.byteLength;
751
- }
752
- if (typeof chunk === "string") {
753
- return chunk.length;
754
- }
755
- return 1;
756
- };
757
- const dataQueue = [];
758
- let dataQueueIndex = 0;
759
- let queuedSize = 0;
760
- let resolveNext = null;
761
- let rejectNext = null;
762
- let done = false;
763
- let pausedByIterator = false;
764
- let streamError = null;
765
- const dataHandler = (chunk) => {
766
- // data events are already encoding-aware; do not decode again here.
767
- if (resolveNext) {
768
- resolveNext(chunk);
769
- resolveNext = null;
770
- rejectNext = null;
771
- }
772
- else {
773
- dataQueue.push(chunk);
774
- }
775
- queuedSize += chunkSizeForBackpressure(chunk);
776
- if (!pausedByIterator && queuedSize >= highWaterMark) {
777
- pausedByIterator = true;
778
- this.pause();
779
- }
780
- };
781
- const endHandler = () => {
782
- done = true;
783
- if (resolveNext) {
784
- resolveNext(null);
785
- resolveNext = null;
786
- rejectNext = null;
787
- }
788
- };
789
- const closeHandler = () => {
790
- done = true;
791
- if (resolveNext) {
792
- resolveNext(null);
793
- resolveNext = null;
794
- rejectNext = null;
795
- }
796
- };
797
- const errorHandler = (err) => {
798
- done = true;
799
- streamError = err;
800
- if (rejectNext) {
801
- rejectNext(err);
802
- resolveNext = null;
803
- rejectNext = null;
804
- }
805
- };
806
- this.on("data", dataHandler);
807
- this.on("end", endHandler);
808
- this.on("error", errorHandler);
809
- this.on("close", closeHandler);
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
- if (dataQueueIndex < dataQueue.length) {
818
- const chunk = dataQueue[dataQueueIndex++];
819
- queuedSize -= chunkSizeForBackpressure(chunk);
820
- if (dataQueueIndex >= 1024 && dataQueueIndex * 2 >= dataQueue.length) {
821
- dataQueue.splice(0, dataQueueIndex);
822
- dataQueueIndex = 0;
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 (pausedByIterator && queuedSize <= lowWaterMark && !done && !this._destroyed) {
825
- pausedByIterator = false;
826
- this.resume();
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
- const chunk = await new Promise((resolve, reject) => {
835
- resolveNext = resolve;
836
- rejectNext = reject;
837
- });
838
- if (chunk !== null) {
839
- queuedSize -= chunkSizeForBackpressure(chunk);
840
- if (pausedByIterator && queuedSize <= lowWaterMark && !done && !this._destroyed) {
841
- pausedByIterator = false;
842
- this.resume();
843
- }
844
- yield chunk;
845
- }
846
- }
847
- if (streamError) {
848
- throw streamError;
723
+ yield value;
849
724
  }
850
725
  }
851
726
  finally {
852
- this.off("data", dataHandler);
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
- const ok = this._doWrite(chunk, cb);
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
- const writer = this._getWriter();
1260
- writer
1097
+ this._getWriter()
1261
1098
  .write(chunk)
1262
1099
  .then(() => {
1263
1100
  this._pendingWrites--;
1264
1101
  this._writableLength -= chunkSize;
1265
- if (this._needDrain && this._writableLength < this.writableHighWaterMark) {
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
- // Avoid double-emitting if we're already in an errored/destroyed state.
1275
- if (!this._destroyed) {
1276
- this._errored = err;
1277
- this.emit("error", err);
1278
- }
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 writer.write(chunk);
1312
- }
1313
- await writer.close();
1314
- if (this._writer === writer) {
1315
- this._writer = null;
1316
- try {
1317
- writer.releaseLock();
1318
- }
1319
- catch {
1320
- // Ignore
1321
- }
1322
- }
1323
- // If we own the underlying Web WritableStream, its `close()` handler already
1324
- // emits finish/close. For external streams, we must emit finish ourselves.
1325
- if (!this._ownsStream) {
1326
- this._finished = true;
1327
- this.emit("finish");
1328
- if (this.emitClose) {
1329
- this.emit("close");
1330
- }
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 && !this._errored) {
1165
+ if (error) {
1353
1166
  this._errored = error;
1354
1167
  this.emit("error", error);
1355
1168
  }
1356
1169
  if (this._writer) {
1357
- const writer = this._writer;
1358
- this._writer = null;
1359
- writer
1360
- .abort(error)
1361
- .catch(() => { })
1362
- .finally(() => {
1363
- try {
1364
- writer.releaseLock();
1365
- }
1366
- catch {
1367
- // Ignore
1368
- }
1369
- });
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
- * Intended to be called from within transform/flush.
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
- return this._readable.push(chunk);
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
- this._endTimer = null;
1533
- this._webStream = null;
1534
- this._sideForwardingCleanup = null;
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
- this._transformImpl = options?.transform;
1537
- this._flushImpl = options?.flush;
1538
- this._readable = new Readable({
1539
- objectMode: this.objectMode
1540
- });
1541
- this._writable = new Writable({
1542
- objectMode: this.objectMode,
1543
- write: (chunk, _encoding, callback) => {
1544
- this._runTransform(chunk)
1545
- .then(() => callback(null))
1546
- .catch(err => callback(err));
1547
- },
1548
- final: callback => {
1549
- this._runFlush()
1550
- .then(() => {
1551
- this._readable.push(null);
1552
- callback(null);
1553
- })
1554
- .catch(err => callback(err));
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._writable.end();
1589
- }, 0);
1590
- }
1591
- _emitErrorOnce(err) {
1592
- if (this._errored) {
1593
- return;
1594
- }
1595
- this._errored = true;
1596
- const error = err instanceof Error ? err : new Error(String(err));
1597
- this.emit("error", error);
1598
- if (!this._destroyed) {
1599
- this._destroyed = true;
1600
- this._readable.destroy(error);
1601
- this._writable.destroy(error);
1602
- queueMicrotask(() => this.emit("close"));
1603
- }
1604
- }
1605
- _hasSubclassTransform() {
1606
- if (this._transformImpl) {
1607
- return false;
1608
- }
1609
- const proto = Object.getPrototypeOf(this);
1610
- return proto._transform !== Transform.prototype._transform;
1611
- }
1612
- _hasSubclassFlush() {
1613
- if (this._flushImpl) {
1614
- return false;
1615
- }
1616
- const proto = Object.getPrototypeOf(this);
1617
- return proto._flush !== Transform.prototype._flush;
1618
- }
1619
- async _runTransform(chunk) {
1620
- if (this._destroyed || this._errored) {
1621
- throw new Error(this._errored ? "Cannot write after stream errored" : "Cannot write after stream destroyed");
1622
- }
1623
- try {
1624
- if (this._hasSubclassTransform()) {
1625
- await new Promise((resolve, reject) => {
1626
- this._transform(chunk, "utf8", (err, data) => {
1627
- if (err) {
1628
- reject(err);
1629
- return;
1630
- }
1631
- if (data !== undefined) {
1632
- this.push(data);
1633
- }
1634
- resolve();
1635
- });
1636
- });
1637
- return;
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
- const userTransform = this._transformImpl;
1640
- if (!userTransform) {
1641
- this.push(chunk);
1642
- return;
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 paramCount = userTransform.length;
1645
- if (paramCount >= 3) {
1646
- await new Promise((resolve, reject) => {
1647
- userTransform.call(this, chunk, "utf8", (err, data) => {
1648
- if (err) {
1649
- reject(err);
1650
- return;
1651
- }
1652
- if (data !== undefined) {
1653
- this.push(data);
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
- resolve();
1656
- });
1657
- });
1658
- return;
1659
- }
1660
- if (paramCount === 2) {
1661
- await new Promise((resolve, reject) => {
1662
- userTransform.call(this, chunk, (err, data) => {
1663
- if (err) {
1664
- reject(err);
1665
- return;
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
- if (data !== undefined) {
1668
- this.push(data);
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
- resolve();
1671
- });
1672
- });
1673
- return;
1674
- }
1675
- const result = userTransform.call(this, chunk);
1676
- if (result && typeof result.then === "function") {
1677
- const awaited = await result;
1678
- if (awaited !== undefined) {
1679
- this.push(awaited);
1479
+ }
1480
+ else {
1481
+ // Default: pass through
1482
+ controller.enqueue(chunk);
1483
+ }
1680
1484
  }
1681
- return;
1682
- }
1683
- if (result !== undefined) {
1684
- this.push(result);
1685
- }
1686
- }
1687
- catch (err) {
1688
- this._emitErrorOnce(err);
1689
- throw err;
1690
- }
1691
- }
1692
- async _runFlush() {
1693
- if (this._destroyed || this._errored) {
1694
- return;
1695
- }
1696
- try {
1697
- if (this._hasSubclassFlush()) {
1698
- await new Promise((resolve, reject) => {
1699
- this._flush((err, data) => {
1700
- if (err) {
1701
- reject(err);
1702
- return;
1703
- }
1704
- if (data !== undefined) {
1705
- this.push(data);
1706
- }
1707
- resolve();
1708
- });
1709
- });
1710
- return;
1711
- }
1712
- const userFlush = this._flushImpl;
1713
- if (!userFlush) {
1714
- return;
1715
- }
1716
- const paramCount = userFlush.length;
1717
- if (paramCount >= 1) {
1718
- await new Promise((resolve, reject) => {
1719
- userFlush.call(this, (err, data) => {
1720
- if (err) {
1721
- reject(err);
1722
- return;
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
- if (data !== undefined) {
1725
- this.push(data);
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
- resolve();
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
- return;
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
- if (result !== undefined && result !== null) {
1741
- this.push(result);
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() to lazily forward readable 'data' events.
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", chunk => this.emit("data", chunk));
1591
+ this._readable.on("data", data => this.emit("data", data));
1757
1592
  }
1758
- return super.on(event, listener);
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
- // If end() has been requested, keep the close deferred as long as writes continue.
1763
- if (this._ended && !this._writable.writableEnded) {
1764
- this._scheduleEnd();
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
- this._scheduleEnd();
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 readable side to destination
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
- if (this._webStream) {
1848
- return this._webStream;
1849
- }
1850
- // Web Streams interop layer.
1851
- const iterator = this[Symbol.asyncIterator]();
1852
- const readable = new ReadableStream({
1853
- pull: async (controller) => {
1854
- const { done, value } = await iterator.next();
1855
- if (done) {
1856
- controller.close();
1857
- return;
1858
- }
1859
- controller.enqueue(value);
1860
- },
1861
- cancel: reason => {
1862
- this.destroy(reason instanceof Error ? reason : new Error(String(reason)));
1863
- }
1864
- });
1865
- const writable = new WritableStream({
1866
- write: chunk => new Promise((resolve, reject) => {
1867
- this.write(chunk, err => {
1868
- if (err) {
1869
- reject(err);
1870
- }
1871
- else {
1872
- resolve();
1873
- }
1874
- });
1875
- }),
1876
- close: () => new Promise(resolve => {
1877
- this.end(() => resolve());
1878
- }),
1879
- abort: reason => {
1880
- this.destroy(reason instanceof Error ? reason : new Error(String(reason)));
1881
- }
1882
- });
1883
- this._webStream = { readable, writable };
1884
- return this._webStream;
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
- transform._webStream = webStream;
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
- if (transform._sideForwardingCleanup) {
1941
- transform._sideForwardingCleanup();
1942
- transform._sideForwardingCleanup = null;
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 (data forwarding remains lazy via Transform.on)
1947
- transform._setupSideForwarding();
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
- const onError = (err) => {
2004
- duplex.emit("error", err);
2005
- };
2006
- const cleanupError = addEmitterListener(readable, "error", onError);
2007
- addEmitterListener(readable, "end", cleanupError, { once: true });
2008
- addEmitterListener(readable, "close", cleanupError, { once: true });
2009
- addEmitterListener(sink, "finish", cleanupError, { once: true });
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
- // Create one duplex that can bridge both sides.
2019
- // (Previous behavior returned a new writable-only Duplex and dropped the readable side.)
2020
- const duplex = new Duplex({
2021
- readableObjectMode: pair.readable?.readableObjectMode,
2022
- writableObjectMode: pair.writable?.writableObjectMode,
2023
- write: pair.writable
2024
- ? (chunk, encoding, callback) => {
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
- : undefined,
2028
- final: pair.writable
2029
- ? callback => {
1915
+ },
1916
+ final(callback) {
2030
1917
  pair.writable.end(callback);
2031
1918
  }
2032
- : undefined
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
- const newReadable = new Readable({
2074
- stream: pair.readable,
2075
- objectMode: duplex.readableObjectMode
2076
- });
2077
- const newWritable = new Writable({
2078
- stream: pair.writable,
2079
- objectMode: duplex.writableObjectMode
2080
- });
2081
- if (duplex._sideForwardingCleanup) {
2082
- duplex._sideForwardingCleanup();
2083
- duplex._sideForwardingCleanup = null;
2084
- }
2085
- duplex._readable = newReadable;
2086
- duplex._writable = newWritable;
2087
- // Re-wire event forwarding (data forwarding remains lazy via Duplex.on)
2088
- duplex._setupSideForwarding();
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
- registry.once(this._readable, "end", () => {
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
- registry.add(this._readable, "error", err => this.emit("error", err));
2137
- registry.add(this._writable, "error", err => this.emit("error", err));
2138
- registry.once(this._writable, "finish", () => this.emit("finish"));
2139
- registry.add(this._writable, "drain", () => this.emit("drain"));
2140
- registry.once(this._writable, "close", () => {
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
- // Readable already supports Node-style `read()` via the constructor option.
2431
- // Keep this helper minimal to avoid accidental double-read behavior.
2432
- return new Readable(options);
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
- pumpAsyncIterableToReadable(readable, iterable);
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
- // Writable already supports Node-style `write()` / `final()` via the constructor.
2469
- return new Writable(options);
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 registry = createListenerRegistry();
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
- cleanupWithSignal(new Error("Pipeline aborted"));
2533
+ cleanup(new Error("Pipeline aborted"));
2632
2534
  return;
2633
2535
  }
2634
- onAbort = () => cleanupWithSignal(new Error("Pipeline aborted"));
2635
- options.signal.addEventListener("abort", onAbort);
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
- let paused = false;
2650
- let waitingForDrain = false;
2651
- const onDrain = () => {
2652
- waitingForDrain = false;
2653
- if (paused && typeof current.resume === "function") {
2654
- paused = false;
2655
- current.resume();
2656
- }
2657
- };
2658
- const onData = (chunk) => {
2659
- const ok = destination.write(chunk);
2660
- if (!ok && !waitingForDrain) {
2661
- waitingForDrain = true;
2662
- if (!paused && typeof current.pause === "function") {
2663
- paused = true;
2664
- current.pause();
2665
- }
2666
- registry.once(destination, "drain", onDrain);
2667
- }
2668
- };
2669
- const onEnd = () => cleanupWithSignal();
2670
- registry.add(current, "data", onData);
2671
- registry.once(current, "end", onEnd);
2552
+ current.on("data", chunk => destination.write(chunk));
2672
2553
  }
2673
2554
  // Handle completion
2674
- registry.once(destination, "finish", () => cleanupWithSignal());
2555
+ destination.on("finish", () => cleanup());
2675
2556
  // Handle errors on all streams
2676
2557
  for (const stream of allStreams) {
2677
- registry.once(stream, "error", (err) => cleanupWithSignal(err));
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
- onAbort = () => done(new Error("Aborted"));
2745
- options.signal.addEventListener("abort", onAbort);
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
- registry.once(normalizedStream, "finish", () => done());
2633
+ normalizedStream.on("finish", () => done());
2761
2634
  }
2762
2635
  if (checkReadable) {
2763
- registry.once(normalizedStream, "end", () => done());
2636
+ normalizedStream.on("end", () => done());
2764
2637
  }
2765
- registry.once(normalizedStream, "error", (err) => done(err));
2766
- registry.once(normalizedStream, "close", () => done());
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
- const { chunks, totalLength } = await collectStreamChunks(stream);
2789
- return concatWithLength(chunks, totalLength);
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 { chunks, totalLength } = await collectStreamChunks(stream);
2800
- const combined = concatWithLength(chunks, totalLength);
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
- const onClose = () => {
2899
- cleanup();
2900
- };
2901
- signal.addEventListener("abort", onAbort);
2902
- addEmitterListener(stream, "close", onClose, { once: true });
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 an underlying readable is provided, forward it into the duplex readable side.
2814
+ // If custom readable/writable provided, pipe them
2950
2815
  if (options?.readable) {
2951
2816
  const readable = options.readable;
2952
- const sink = new Writable({
2953
- objectMode: duplex.readableObjectMode,
2954
- write(chunk, _encoding, callback) {
2955
- duplex.push(chunk);
2956
- callback(null);
2957
- },
2958
- final(callback) {
2959
- duplex.push(null);
2960
- callback(null);
2961
- }
2962
- });
2963
- if (typeof readable?.on === "function") {
2964
- const onError = (err) => {
2965
- duplex.destroy(err);
2966
- };
2967
- const cleanupError = addEmitterListener(readable, "error", onError);
2968
- addEmitterListener(readable, "end", cleanupError, { once: true });
2969
- addEmitterListener(readable, "close", cleanupError, { once: true });
2970
- addEmitterListener(sink, "finish", cleanupError, { once: true });
2971
- }
2972
- readable.pipe?.(sink);
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
- pumpAsyncIterableToReadable(readable, generator());
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
- // Preserve identity: compose(single) returns the same transform.
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(options) {
3039
- super(options);
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
- const onData = (chunk) => {
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
- const onEnd = () => {
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