@cj-tech-master/excelts 4.2.0 → 4.2.1-canary.20260111102127.f808a37

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