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