@cj-tech-master/excelts 4.2.0 → 4.2.1-canary.20260112134913.a3cecdd

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 (317) 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 +8 -0
  29. package/dist/browser/modules/archive/io/archive-sink.js +45 -0
  30. package/dist/browser/modules/archive/io/archive-source.d.ts +6 -0
  31. package/dist/browser/modules/archive/io/archive-source.js +100 -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/utils/ooxml-validator.d.ts +48 -0
  69. package/dist/browser/modules/excel/utils/ooxml-validator.js +469 -0
  70. package/dist/browser/modules/excel/worksheet.js +5 -2
  71. package/dist/browser/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.d.ts +1 -0
  72. package/dist/browser/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.js +13 -1
  73. package/dist/browser/modules/excel/xlsx/xform/drawing/sp-xform.d.ts +18 -0
  74. package/dist/browser/modules/excel/xlsx/xform/drawing/sp-xform.js +112 -0
  75. package/dist/browser/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.d.ts +6 -1
  76. package/dist/browser/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +30 -2
  77. package/dist/browser/modules/excel/xlsx/xform/drawing/vml-drawing-xform.js +11 -0
  78. package/dist/browser/modules/excel/xlsx/xform/sheet/page-setup-xform.d.ts +1 -0
  79. package/dist/browser/modules/excel/xlsx/xform/sheet/page-setup-xform.js +16 -2
  80. package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +117 -5
  81. package/dist/browser/modules/excel/xlsx/xlsx.browser.js +3 -6
  82. package/dist/browser/modules/excel/xlsx/xlsx.js +1 -1
  83. package/dist/browser/modules/stream/base-transform.d.ts +3 -0
  84. package/dist/browser/modules/stream/base-transform.js +34 -20
  85. package/dist/browser/modules/stream/buffered-stream.d.ts +2 -12
  86. package/dist/browser/modules/stream/chunked-builder.js +4 -4
  87. package/dist/browser/modules/stream/index.browser.d.ts +13 -19
  88. package/dist/browser/modules/stream/index.browser.js +10 -22
  89. package/dist/browser/modules/stream/index.d.ts +18 -41
  90. package/dist/browser/modules/stream/index.js +15 -44
  91. package/dist/browser/modules/stream/internal/event-utils.d.ts +17 -0
  92. package/dist/browser/modules/stream/internal/event-utils.js +40 -0
  93. package/dist/browser/modules/stream/internal/type-guards.d.ts +9 -0
  94. package/dist/browser/modules/stream/internal/type-guards.js +24 -0
  95. package/dist/browser/modules/stream/pull-stream.d.ts +5 -6
  96. package/dist/browser/modules/stream/pull-stream.js +107 -43
  97. package/dist/browser/modules/stream/shared.d.ts +1 -1
  98. package/dist/browser/modules/stream/shared.js +7 -4
  99. package/dist/browser/modules/stream/streams.browser.d.ts +32 -42
  100. package/dist/browser/modules/stream/streams.browser.js +941 -823
  101. package/dist/browser/modules/stream/streams.d.ts +4 -20
  102. package/dist/browser/modules/stream/streams.js +146 -95
  103. package/dist/browser/modules/stream/utils.js +5 -38
  104. package/dist/cjs/modules/archive/{compress.base.js → compression/compress.base.js} +2 -1
  105. package/dist/cjs/modules/archive/{compress.browser.js → compression/compress.browser.js} +18 -19
  106. package/dist/cjs/modules/archive/{compress.js → compression/compress.js} +1 -1
  107. package/dist/cjs/modules/archive/{crc32.js → compression/crc32.js} +1 -1
  108. package/dist/cjs/modules/archive/{deflate-fallback.js → compression/deflate-fallback.js} +1 -1
  109. package/dist/cjs/modules/archive/{streaming-compress.browser.js → compression/streaming-compress.browser.js} +3 -3
  110. package/dist/cjs/modules/archive/{streaming-compress.js → compression/streaming-compress.js} +2 -2
  111. package/dist/cjs/modules/archive/defaults.js +7 -4
  112. package/dist/cjs/modules/archive/index.base.js +9 -19
  113. package/dist/cjs/modules/archive/index.browser.js +4 -10
  114. package/dist/cjs/modules/archive/index.js +4 -8
  115. package/dist/cjs/modules/archive/internal/byte-queue.js +411 -0
  116. package/dist/cjs/modules/archive/io/archive-sink.js +49 -0
  117. package/dist/cjs/modules/archive/io/archive-source.js +105 -0
  118. package/dist/cjs/modules/archive/unzip/index.js +170 -0
  119. package/dist/cjs/modules/archive/unzip/stream.base.js +1044 -0
  120. package/dist/cjs/modules/archive/{parse.browser.js → unzip/stream.browser.js} +377 -111
  121. package/dist/cjs/modules/archive/{parse.js → unzip/stream.js} +9 -8
  122. package/dist/cjs/modules/archive/{zip-parser.js → unzip/zip-parser.js} +47 -33
  123. package/dist/cjs/modules/archive/utils/async-queue.js +106 -0
  124. package/dist/cjs/modules/archive/utils/bytes.js +16 -16
  125. package/dist/cjs/modules/archive/utils/compressibility.js +60 -0
  126. package/dist/cjs/modules/archive/utils/parse-buffer.js +21 -23
  127. package/dist/cjs/modules/archive/utils/pattern-scanner.js +31 -0
  128. package/dist/cjs/modules/archive/utils/timestamps.js +64 -3
  129. package/dist/cjs/modules/archive/utils/zip-extra-fields.js +26 -14
  130. package/dist/cjs/modules/archive/zip/index.js +162 -0
  131. package/dist/cjs/modules/archive/{streaming-zip.js → zip/stream.js} +194 -50
  132. package/dist/cjs/modules/archive/zip/zip-bytes.js +242 -0
  133. package/dist/cjs/modules/archive/{zip-entry-metadata.js → zip/zip-entry-metadata.js} +5 -5
  134. package/dist/cjs/modules/archive/zip-spec/zip-records.js +136 -0
  135. package/dist/cjs/modules/excel/form-control.js +54 -16
  136. package/dist/cjs/modules/excel/stream/workbook-reader.browser.js +2 -2
  137. package/dist/cjs/modules/excel/stream/workbook-writer.browser.js +4 -4
  138. package/dist/cjs/modules/excel/utils/ooxml-validator.js +475 -0
  139. package/dist/cjs/modules/excel/worksheet.js +5 -2
  140. package/dist/cjs/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.js +13 -1
  141. package/dist/cjs/modules/excel/xlsx/xform/drawing/sp-xform.js +115 -0
  142. package/dist/cjs/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +30 -2
  143. package/dist/cjs/modules/excel/xlsx/xform/drawing/vml-drawing-xform.js +11 -0
  144. package/dist/cjs/modules/excel/xlsx/xform/sheet/page-setup-xform.js +16 -2
  145. package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +117 -5
  146. package/dist/cjs/modules/excel/xlsx/xlsx.browser.js +6 -9
  147. package/dist/cjs/modules/excel/xlsx/xlsx.js +2 -2
  148. package/dist/cjs/modules/stream/base-transform.js +34 -20
  149. package/dist/cjs/modules/stream/chunked-builder.js +4 -4
  150. package/dist/cjs/modules/stream/index.browser.js +10 -17
  151. package/dist/cjs/modules/stream/index.js +15 -39
  152. package/dist/cjs/modules/stream/internal/event-utils.js +43 -0
  153. package/dist/cjs/modules/stream/internal/type-guards.js +30 -0
  154. package/dist/cjs/modules/stream/pull-stream.js +107 -43
  155. package/dist/cjs/modules/stream/shared.js +7 -4
  156. package/dist/cjs/modules/stream/streams.browser.js +947 -834
  157. package/dist/cjs/modules/stream/streams.js +156 -107
  158. package/dist/cjs/modules/stream/utils.js +3 -36
  159. package/dist/esm/index.browser.js +12 -0
  160. package/dist/esm/modules/archive/{compress.base.js → compression/compress.base.js} +2 -1
  161. package/dist/{browser/modules/archive → esm/modules/archive/compression}/compress.browser.js +18 -19
  162. package/dist/esm/modules/archive/{compress.js → compression/compress.js} +1 -1
  163. package/dist/esm/modules/archive/{crc32.js → compression/crc32.js} +1 -1
  164. package/dist/esm/modules/archive/{deflate-fallback.js → compression/deflate-fallback.js} +1 -1
  165. package/dist/esm/modules/archive/{streaming-compress.browser.js → compression/streaming-compress.browser.js} +3 -3
  166. package/dist/esm/modules/archive/{streaming-compress.js → compression/streaming-compress.js} +2 -2
  167. package/dist/esm/modules/archive/defaults.js +6 -3
  168. package/dist/esm/modules/archive/index.base.js +3 -6
  169. package/dist/esm/modules/archive/index.browser.js +3 -7
  170. package/dist/esm/modules/archive/index.js +3 -5
  171. package/dist/esm/modules/archive/internal/byte-queue.js +407 -0
  172. package/dist/esm/modules/archive/io/archive-sink.js +45 -0
  173. package/dist/esm/modules/archive/io/archive-source.js +100 -0
  174. package/dist/esm/modules/archive/unzip/index.js +164 -0
  175. package/dist/esm/modules/archive/unzip/stream.base.js +1022 -0
  176. package/dist/esm/modules/archive/{parse.browser.js → unzip/stream.browser.js} +376 -110
  177. package/dist/{browser/modules/archive/parse.js → esm/modules/archive/unzip/stream.js} +7 -6
  178. package/dist/{browser/modules/archive → esm/modules/archive/unzip}/zip-parser.js +38 -24
  179. package/dist/esm/modules/archive/utils/async-queue.js +103 -0
  180. package/dist/esm/modules/archive/utils/bytes.js +16 -16
  181. package/dist/esm/modules/archive/utils/compressibility.js +57 -0
  182. package/dist/esm/modules/archive/utils/parse-buffer.js +21 -23
  183. package/dist/esm/modules/archive/utils/pattern-scanner.js +27 -0
  184. package/dist/esm/modules/archive/utils/timestamps.js +62 -1
  185. package/dist/esm/modules/archive/utils/zip-extra-fields.js +26 -14
  186. package/dist/esm/modules/archive/zip/index.js +157 -0
  187. package/dist/esm/modules/archive/{streaming-zip.js → zip/stream.js} +192 -48
  188. package/dist/esm/modules/archive/zip/zip-bytes.js +239 -0
  189. package/dist/{browser/modules/archive → esm/modules/archive/zip}/zip-entry-metadata.js +3 -3
  190. package/dist/esm/modules/archive/zip-spec/zip-records.js +126 -0
  191. package/dist/esm/modules/excel/form-control.js +54 -16
  192. package/dist/esm/modules/excel/stream/workbook-reader.browser.js +1 -1
  193. package/dist/esm/modules/excel/stream/workbook-writer.browser.js +1 -1
  194. package/dist/esm/modules/excel/utils/ooxml-validator.js +469 -0
  195. package/dist/esm/modules/excel/worksheet.js +5 -2
  196. package/dist/esm/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.js +13 -1
  197. package/dist/esm/modules/excel/xlsx/xform/drawing/sp-xform.js +112 -0
  198. package/dist/esm/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +30 -2
  199. package/dist/esm/modules/excel/xlsx/xform/drawing/vml-drawing-xform.js +11 -0
  200. package/dist/esm/modules/excel/xlsx/xform/sheet/page-setup-xform.js +16 -2
  201. package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +117 -5
  202. package/dist/esm/modules/excel/xlsx/xlsx.browser.js +3 -6
  203. package/dist/esm/modules/excel/xlsx/xlsx.js +1 -1
  204. package/dist/esm/modules/stream/base-transform.js +34 -20
  205. package/dist/esm/modules/stream/chunked-builder.js +4 -4
  206. package/dist/esm/modules/stream/index.browser.js +10 -22
  207. package/dist/esm/modules/stream/index.js +15 -44
  208. package/dist/esm/modules/stream/internal/event-utils.js +40 -0
  209. package/dist/esm/modules/stream/internal/type-guards.js +24 -0
  210. package/dist/esm/modules/stream/pull-stream.js +107 -43
  211. package/dist/esm/modules/stream/shared.js +7 -4
  212. package/dist/esm/modules/stream/streams.browser.js +941 -823
  213. package/dist/esm/modules/stream/streams.js +146 -95
  214. package/dist/esm/modules/stream/utils.js +5 -38
  215. package/dist/iife/THIRD_PARTY_NOTICES.md +81 -0
  216. package/dist/iife/excelts.iife.js +4979 -2800
  217. package/dist/iife/excelts.iife.js.map +1 -1
  218. package/dist/iife/excelts.iife.min.js +103 -31
  219. package/dist/types/index.browser.d.ts +1 -0
  220. package/dist/{browser/modules/archive → types/modules/archive/compression}/compress.base.d.ts +1 -0
  221. package/dist/{browser/modules/archive → types/modules/archive/compression}/compress.browser.d.ts +10 -8
  222. package/dist/types/modules/archive/{streaming-compress.browser.d.ts → compression/streaming-compress.browser.d.ts} +1 -1
  223. package/dist/types/modules/archive/defaults.d.ts +1 -0
  224. package/dist/types/modules/archive/index.base.d.ts +4 -4
  225. package/dist/types/modules/archive/index.browser.d.ts +3 -4
  226. package/dist/types/modules/archive/index.d.ts +3 -4
  227. package/dist/types/modules/archive/internal/byte-queue.d.ts +33 -0
  228. package/dist/types/modules/archive/io/archive-sink.d.ts +8 -0
  229. package/dist/types/modules/archive/io/archive-source.d.ts +6 -0
  230. package/dist/types/modules/archive/unzip/index.d.ts +40 -0
  231. package/dist/types/modules/archive/{parse.base.d.ts → unzip/stream.base.d.ts} +60 -5
  232. package/dist/types/modules/archive/{parse.browser.d.ts → unzip/stream.browser.d.ts} +2 -2
  233. package/dist/types/modules/archive/{parse.d.ts → unzip/stream.d.ts} +3 -3
  234. package/dist/{browser/modules/archive → types/modules/archive/unzip}/zip-parser.d.ts +1 -1
  235. package/dist/types/modules/archive/utils/async-queue.d.ts +7 -0
  236. package/dist/types/modules/archive/utils/compressibility.d.ts +10 -0
  237. package/dist/types/modules/archive/utils/pattern-scanner.d.ts +21 -0
  238. package/dist/types/modules/archive/utils/zip-extra-fields.d.ts +1 -1
  239. package/dist/types/modules/archive/zip/index.d.ts +42 -0
  240. package/dist/types/modules/archive/{streaming-zip.d.ts → zip/stream.d.ts} +29 -6
  241. package/dist/types/modules/archive/zip/zip-bytes.d.ts +73 -0
  242. package/dist/types/modules/archive/{zip-entry-metadata.d.ts → zip/zip-entry-metadata.d.ts} +1 -1
  243. package/dist/types/modules/archive/{zip-records.d.ts → zip-spec/zip-records.d.ts} +20 -0
  244. package/dist/types/modules/excel/form-control.d.ts +2 -0
  245. package/dist/types/modules/excel/stream/workbook-writer.browser.d.ts +1 -1
  246. package/dist/types/modules/excel/utils/ooxml-validator.d.ts +48 -0
  247. package/dist/types/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.d.ts +1 -0
  248. package/dist/types/modules/excel/xlsx/xform/drawing/sp-xform.d.ts +18 -0
  249. package/dist/types/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.d.ts +6 -1
  250. package/dist/types/modules/excel/xlsx/xform/sheet/page-setup-xform.d.ts +1 -0
  251. package/dist/types/modules/stream/base-transform.d.ts +3 -0
  252. package/dist/types/modules/stream/buffered-stream.d.ts +2 -12
  253. package/dist/types/modules/stream/index.browser.d.ts +13 -19
  254. package/dist/types/modules/stream/index.d.ts +18 -41
  255. package/dist/types/modules/stream/internal/event-utils.d.ts +17 -0
  256. package/dist/types/modules/stream/internal/type-guards.d.ts +9 -0
  257. package/dist/types/modules/stream/pull-stream.d.ts +5 -6
  258. package/dist/types/modules/stream/shared.d.ts +1 -1
  259. package/dist/types/modules/stream/streams.browser.d.ts +32 -42
  260. package/dist/types/modules/stream/streams.d.ts +4 -20
  261. package/package.json +19 -15
  262. package/dist/browser/modules/archive/byte-queue.d.ts +0 -18
  263. package/dist/browser/modules/archive/byte-queue.js +0 -125
  264. package/dist/browser/modules/archive/parse.base.js +0 -610
  265. package/dist/browser/modules/archive/utils/zip-extra.d.ts +0 -18
  266. package/dist/browser/modules/archive/utils/zip-extra.js +0 -68
  267. package/dist/browser/modules/archive/zip-builder.d.ts +0 -117
  268. package/dist/browser/modules/archive/zip-builder.js +0 -292
  269. package/dist/browser/modules/archive/zip-constants.d.ts +0 -18
  270. package/dist/browser/modules/archive/zip-constants.js +0 -23
  271. package/dist/browser/modules/archive/zip-records.js +0 -84
  272. package/dist/cjs/modules/archive/byte-queue.js +0 -129
  273. package/dist/cjs/modules/archive/parse.base.js +0 -632
  274. package/dist/cjs/modules/archive/utils/zip-extra.js +0 -74
  275. package/dist/cjs/modules/archive/zip-builder.js +0 -297
  276. package/dist/cjs/modules/archive/zip-constants.js +0 -26
  277. package/dist/cjs/modules/archive/zip-records.js +0 -90
  278. package/dist/esm/modules/archive/byte-queue.js +0 -125
  279. package/dist/esm/modules/archive/parse.base.js +0 -610
  280. package/dist/esm/modules/archive/utils/zip-extra.js +0 -68
  281. package/dist/esm/modules/archive/zip-builder.js +0 -292
  282. package/dist/esm/modules/archive/zip-constants.js +0 -23
  283. package/dist/esm/modules/archive/zip-records.js +0 -84
  284. package/dist/types/modules/archive/byte-queue.d.ts +0 -18
  285. package/dist/types/modules/archive/utils/zip-extra.d.ts +0 -18
  286. package/dist/types/modules/archive/zip-builder.d.ts +0 -117
  287. package/dist/types/modules/archive/zip-constants.d.ts +0 -18
  288. /package/dist/browser/modules/archive/{crc32.base.d.ts → compression/crc32.base.d.ts} +0 -0
  289. /package/dist/browser/modules/archive/{crc32.base.js → compression/crc32.base.js} +0 -0
  290. /package/dist/browser/modules/archive/{crc32.browser.js → compression/crc32.browser.js} +0 -0
  291. /package/dist/browser/modules/archive/{deflate-fallback.d.ts → compression/deflate-fallback.d.ts} +0 -0
  292. /package/dist/browser/modules/archive/{streaming-compress.base.d.ts → compression/streaming-compress.base.d.ts} +0 -0
  293. /package/dist/browser/modules/archive/{streaming-compress.base.js → compression/streaming-compress.base.js} +0 -0
  294. /package/dist/browser/modules/archive/{extract.js → unzip/extract.js} +0 -0
  295. /package/dist/browser/modules/archive/{zip-entry-metadata.d.ts → zip/zip-entry-metadata.d.ts} +0 -0
  296. /package/dist/browser/modules/archive/{zip-entry-info.d.ts → zip-spec/zip-entry-info.d.ts} +0 -0
  297. /package/dist/browser/modules/archive/{zip-entry-info.js → zip-spec/zip-entry-info.js} +0 -0
  298. /package/dist/cjs/modules/archive/{crc32.base.js → compression/crc32.base.js} +0 -0
  299. /package/dist/cjs/modules/archive/{crc32.browser.js → compression/crc32.browser.js} +0 -0
  300. /package/dist/cjs/modules/archive/{streaming-compress.base.js → compression/streaming-compress.base.js} +0 -0
  301. /package/dist/cjs/modules/archive/{extract.js → unzip/extract.js} +0 -0
  302. /package/dist/cjs/modules/archive/{zip-entry-info.js → zip-spec/zip-entry-info.js} +0 -0
  303. /package/dist/esm/modules/archive/{crc32.base.js → compression/crc32.base.js} +0 -0
  304. /package/dist/esm/modules/archive/{crc32.browser.js → compression/crc32.browser.js} +0 -0
  305. /package/dist/esm/modules/archive/{streaming-compress.base.js → compression/streaming-compress.base.js} +0 -0
  306. /package/dist/esm/modules/archive/{extract.js → unzip/extract.js} +0 -0
  307. /package/dist/esm/modules/archive/{zip-entry-info.js → zip-spec/zip-entry-info.js} +0 -0
  308. /package/dist/{LICENSE → iife/LICENSE} +0 -0
  309. /package/dist/types/modules/archive/{compress.d.ts → compression/compress.d.ts} +0 -0
  310. /package/dist/types/modules/archive/{crc32.base.d.ts → compression/crc32.base.d.ts} +0 -0
  311. /package/dist/types/modules/archive/{crc32.browser.d.ts → compression/crc32.browser.d.ts} +0 -0
  312. /package/dist/types/modules/archive/{crc32.d.ts → compression/crc32.d.ts} +0 -0
  313. /package/dist/types/modules/archive/{deflate-fallback.d.ts → compression/deflate-fallback.d.ts} +0 -0
  314. /package/dist/types/modules/archive/{streaming-compress.base.d.ts → compression/streaming-compress.base.d.ts} +0 -0
  315. /package/dist/types/modules/archive/{streaming-compress.d.ts → compression/streaming-compress.d.ts} +0 -0
  316. /package/dist/types/modules/archive/{extract.d.ts → unzip/extract.d.ts} +0 -0
  317. /package/dist/types/modules/archive/{zip-entry-info.d.ts → zip-spec/zip-entry-info.d.ts} +0 -0
@@ -11,9 +11,44 @@
11
11
  * - Edge >= 89
12
12
  */
13
13
  import { EventEmitter } from "./event-emitter.js";
14
- import { PullStream as StandalonePullStream } from "./pull-stream.js";
15
- import { BufferedStream as StandaloneBufferedStream, StringChunk as StandaloneStringChunk, BufferChunk as StandaloneBufferChunk } from "./buffered-stream.js";
14
+ import { PullStream } from "./pull-stream.js";
15
+ import { BufferedStream, BufferChunk, StringChunk } from "./buffered-stream.js";
16
16
  import { concatUint8Arrays, getTextDecoder, textDecoder } from "./shared.js";
17
+ import { isAsyncIterable, isReadableStream, isTransformStream, isWritableStream } from "./internal/type-guards.js";
18
+ const removeEmitterListener = (emitter, event, listener) => {
19
+ if (typeof emitter.off === "function") {
20
+ emitter.off(event, listener);
21
+ }
22
+ else if (typeof emitter.removeListener === "function") {
23
+ emitter.removeListener(event, listener);
24
+ }
25
+ };
26
+ const addEmitterListener = (emitter, event, listener, options) => {
27
+ if (options?.once && typeof emitter.once === "function") {
28
+ emitter.once(event, listener);
29
+ }
30
+ else {
31
+ emitter.on(event, listener);
32
+ }
33
+ return () => removeEmitterListener(emitter, event, listener);
34
+ };
35
+ const createListenerRegistry = () => {
36
+ const listeners = [];
37
+ return {
38
+ add: (emitter, event, listener) => {
39
+ listeners.push(addEmitterListener(emitter, event, listener));
40
+ },
41
+ once: (emitter, event, listener) => {
42
+ listeners.push(addEmitterListener(emitter, event, listener, { once: true }));
43
+ },
44
+ cleanup: () => {
45
+ for (let i = listeners.length - 1; i >= 0; i--) {
46
+ listeners[i]();
47
+ }
48
+ listeners.length = 0;
49
+ }
50
+ };
51
+ };
17
52
  // =============================================================================
18
53
  // Readable Stream Wrapper
19
54
  // =============================================================================
@@ -30,6 +65,7 @@ export class Readable extends EventEmitter {
30
65
  this._bufferSize = 0;
31
66
  this._reading = false;
32
67
  this._ended = false;
68
+ this._endEmitted = false;
33
69
  this._destroyed = false;
34
70
  this._errored = null;
35
71
  this._closed = false;
@@ -86,20 +122,7 @@ export class Readable extends EventEmitter {
86
122
  */
87
123
  static from(iterable, options) {
88
124
  const readable = new Readable({ ...options, objectMode: options?.objectMode ?? true });
89
- (async () => {
90
- try {
91
- for await (const chunk of iterable) {
92
- if (!readable.push(chunk)) {
93
- // Backpressure
94
- await new Promise(resolve => setTimeout(resolve, 0));
95
- }
96
- }
97
- readable.push(null);
98
- }
99
- catch (err) {
100
- readable.destroy(err);
101
- }
102
- })();
125
+ pumpAsyncIterableToReadable(readable, toAsyncIterable(iterable));
103
126
  return readable;
104
127
  }
105
128
  /**
@@ -144,16 +167,30 @@ export class Readable extends EventEmitter {
144
167
  // Controller may already be closed
145
168
  }
146
169
  }
147
- this.emit("end");
170
+ // Emit 'end' only after buffered data is fully drained.
171
+ // This avoids premature 'end' when producers push null while paused.
172
+ if (this._bufferedLength() === 0) {
173
+ this._emitEndOnce();
174
+ }
148
175
  // Note: Don't call destroy() here, let the stream be consumed naturally
149
176
  // The reader will return done:true when it finishes reading
150
177
  return false;
151
178
  }
179
+ // Keep the internal Web ReadableStream in sync for controllable streams.
180
+ // For external Web streams (_webStreamMode=true), push() is not the data source.
181
+ if (controller && !this._webStreamMode) {
182
+ try {
183
+ controller.enqueue(chunk);
184
+ }
185
+ catch {
186
+ // Controller may be closed/errored; Node-side buffering/events still work.
187
+ }
188
+ }
152
189
  if (this._flowing) {
153
190
  // In flowing mode, emit data directly without buffering or enqueueing
154
191
  // const chunkStr = chunk instanceof Uint8Array ? new TextDecoder().decode(chunk.slice(0, 50)) : String(chunk).slice(0, 50);
155
192
  // console.log(`[Readable#${this._id}.push FLOWING] emit data size:${(chunk as any).length || (chunk as any).byteLength} start:"${chunkStr}"`);
156
- this.emit("data", chunk);
193
+ this.emit("data", this._applyEncoding(chunk));
157
194
  // Check if stream was paused during emit (backpressure from consumer)
158
195
  if (!this._flowing) {
159
196
  return false;
@@ -178,10 +215,8 @@ export class Readable extends EventEmitter {
178
215
  if (!this.objectMode) {
179
216
  this._bufferSize += this._getChunkSize(chunk);
180
217
  }
181
- // NOTE: Do NOT enqueue to Web Stream controller here!
182
- // In push mode, _buffer is the only source of data for data events.
183
- // Web Stream is only used for async iteration when not in push mode.
184
- // Enqueueing here would cause data duplication when _startReading is also running.
218
+ // NOTE: We still buffer for Node-style read()/data semantics.
219
+ // The internal Web ReadableStream is also fed via controller.enqueue() above.
185
220
  // Emit readable event when buffer goes from empty to having data
186
221
  if (wasEmpty) {
187
222
  queueMicrotask(() => this.emit("readable"));
@@ -195,6 +230,13 @@ export class Readable extends EventEmitter {
195
230
  return this._bufferSize < this.readableHighWaterMark;
196
231
  }
197
232
  }
233
+ _emitEndOnce() {
234
+ if (this._endEmitted) {
235
+ return;
236
+ }
237
+ this._endEmitted = true;
238
+ this.emit("end");
239
+ }
198
240
  /**
199
241
  * Put a chunk back at the front of the buffer
200
242
  * Note: unshift is allowed even after end, as it's used to put back already read data
@@ -219,14 +261,22 @@ export class Readable extends EventEmitter {
219
261
  if (!this.objectMode) {
220
262
  this._bufferSize -= this._getChunkSize(chunk);
221
263
  }
222
- return this._applyEncoding(chunk);
264
+ const decoded = this._applyEncoding(chunk);
265
+ if (this._ended && this._bufferedLength() === 0) {
266
+ queueMicrotask(() => this._emitEndOnce());
267
+ }
268
+ return decoded;
223
269
  }
224
270
  // For binary mode, handle size
225
271
  const chunk = this._bufferShift();
226
272
  if (!this.objectMode) {
227
273
  this._bufferSize -= this._getChunkSize(chunk);
228
274
  }
229
- return this._applyEncoding(chunk);
275
+ const decoded = this._applyEncoding(chunk);
276
+ if (this._ended && this._bufferedLength() === 0) {
277
+ queueMicrotask(() => this._emitEndOnce());
278
+ }
279
+ return decoded;
230
280
  }
231
281
  return null;
232
282
  }
@@ -332,11 +382,11 @@ export class Readable extends EventEmitter {
332
382
  if (!this.objectMode) {
333
383
  this._bufferSize -= this._getChunkSize(chunk);
334
384
  }
335
- this.emit("data", chunk);
385
+ this.emit("data", this._applyEncoding(chunk));
336
386
  }
337
387
  // If already ended, emit end event
338
388
  if (this._ended && this._bufferedLength() === 0) {
339
- this.emit("end");
389
+ this._emitEndOnce();
340
390
  }
341
391
  else if (this._read) {
342
392
  // Call user-provided read function asynchronously
@@ -384,58 +434,72 @@ export class Readable extends EventEmitter {
384
434
  // causing `instanceof Transform/Writable/Duplex` to fail even when the object
385
435
  // is a valid destination.
386
436
  const dest = destination;
387
- // Get the actual writable target.
388
- // Prefer internal `_writable` (Transform/Duplex wrappers), else treat the destination as writable-like.
389
- const candidate = dest?._writable ?? dest;
390
- const hasWrite = typeof candidate?.write === "function";
391
- const hasEnd = typeof candidate?.end === "function";
392
- const hasOn = typeof candidate?.on === "function";
393
- const hasOnce = typeof candidate?.once === "function";
394
- const hasOff = typeof candidate?.off === "function";
395
- if (!hasWrite || !hasEnd || (!hasOnce && !hasOn) || (!hasOff && !candidate?.removeListener)) {
437
+ // For event handling (drain, once, off), we need the object that emits events.
438
+ // For write/end, we must call the destination's own write()/end() methods,
439
+ // NOT the internal _writable, because Transform.write() has important logic
440
+ // (like auto-consume) that _writable.write() bypasses.
441
+ const eventTarget = dest;
442
+ const hasWrite = typeof dest?.write === "function";
443
+ const hasEnd = typeof dest?.end === "function";
444
+ const hasOn = typeof eventTarget?.on === "function";
445
+ const hasOnce = typeof eventTarget?.once === "function";
446
+ const hasOff = typeof eventTarget?.off === "function";
447
+ if (!hasWrite || !hasEnd || (!hasOnce && !hasOn) || (!hasOff && !eventTarget?.removeListener)) {
396
448
  throw new Error("Readable.pipe: invalid destination");
397
449
  }
398
- const target = candidate;
399
- this._pipeTo.push(target);
450
+ this._pipeTo.push(dest);
400
451
  // Create listeners that we can later remove
452
+ let drainListener;
453
+ const removeDrainListener = () => {
454
+ if (!drainListener) {
455
+ return;
456
+ }
457
+ if (typeof eventTarget.off === "function") {
458
+ eventTarget.off("drain", drainListener);
459
+ }
460
+ else if (typeof eventTarget.removeListener === "function") {
461
+ eventTarget.removeListener("drain", drainListener);
462
+ }
463
+ drainListener = undefined;
464
+ };
401
465
  const dataListener = (chunk) => {
402
- const canWrite = target.write(chunk);
466
+ // Call destination's write() method (not internal _writable.write())
467
+ // This ensures Transform.write() logic runs properly
468
+ const canWrite = dest.write(chunk);
403
469
  if (!canWrite) {
404
470
  this.pause();
405
- if (typeof target.once === "function") {
406
- target.once("drain", () => this.resume());
407
- }
408
- else {
409
- const resumeOnce = () => {
410
- if (typeof target.off === "function") {
411
- target.off("drain", resumeOnce);
412
- }
413
- else if (typeof target.removeListener === "function") {
414
- target.removeListener("drain", resumeOnce);
415
- }
471
+ // Install a removable, once-style drain listener.
472
+ if (!drainListener) {
473
+ drainListener = () => {
474
+ removeDrainListener();
416
475
  this.resume();
417
476
  };
418
- target.on("drain", resumeOnce);
477
+ eventTarget.on("drain", drainListener);
478
+ const entry = this._pipeListeners.get(dest);
479
+ if (entry) {
480
+ entry.drain = drainListener;
481
+ }
419
482
  }
420
483
  }
421
484
  };
422
485
  const endListener = () => {
423
- target.end();
486
+ dest.end();
424
487
  };
425
488
  const errorListener = (err) => {
426
- if (typeof target.destroy === "function") {
427
- target.destroy(err);
489
+ if (typeof dest.destroy === "function") {
490
+ dest.destroy(err);
428
491
  }
429
492
  else {
430
493
  // Best-effort: forward error to the destination if it supports events.
431
- target.emit?.("error", err);
494
+ eventTarget.emit?.("error", err);
432
495
  }
433
496
  };
434
497
  // Store listeners for later removal in unpipe
435
- this._pipeListeners.set(target, {
498
+ this._pipeListeners.set(dest, {
436
499
  data: dataListener,
437
500
  end: endListener,
438
- error: errorListener
501
+ error: errorListener,
502
+ eventTarget
439
503
  });
440
504
  this.on("data", dataListener);
441
505
  this.once("end", endListener);
@@ -458,6 +522,14 @@ export class Readable extends EventEmitter {
458
522
  this.off("data", listeners.data);
459
523
  this.off("end", listeners.end);
460
524
  this.off("error", listeners.error);
525
+ if (listeners.drain) {
526
+ if (typeof listeners.eventTarget?.off === "function") {
527
+ listeners.eventTarget.off("drain", listeners.drain);
528
+ }
529
+ else if (typeof listeners.eventTarget?.removeListener === "function") {
530
+ listeners.eventTarget.removeListener("drain", listeners.drain);
531
+ }
532
+ }
461
533
  this._pipeListeners.delete(destination);
462
534
  }
463
535
  }
@@ -469,6 +541,14 @@ export class Readable extends EventEmitter {
469
541
  this.off("data", listeners.data);
470
542
  this.off("end", listeners.end);
471
543
  this.off("error", listeners.error);
544
+ if (listeners.drain) {
545
+ if (typeof listeners.eventTarget?.off === "function") {
546
+ listeners.eventTarget.off("drain", listeners.drain);
547
+ }
548
+ else if (typeof listeners.eventTarget?.removeListener === "function") {
549
+ listeners.eventTarget.removeListener("drain", listeners.drain);
550
+ }
551
+ }
472
552
  this._pipeListeners.delete(target);
473
553
  }
474
554
  }
@@ -487,12 +567,26 @@ export class Readable extends EventEmitter {
487
567
  }
488
568
  this._destroyed = true;
489
569
  this._ended = true;
570
+ // Ensure we detach from destinations to avoid leaking listeners.
571
+ this.unpipe();
490
572
  if (error) {
491
573
  this._errored = error;
492
574
  this.emit("error", error);
493
575
  }
494
576
  if (this._reader) {
495
- this._reader.cancel().catch(() => { });
577
+ const reader = this._reader;
578
+ this._reader = null;
579
+ reader
580
+ .cancel()
581
+ .catch(() => { })
582
+ .finally(() => {
583
+ try {
584
+ reader.releaseLock();
585
+ }
586
+ catch {
587
+ // Ignore if a read is still pending
588
+ }
589
+ });
496
590
  }
497
591
  this._closed = true;
498
592
  this.emit("close");
@@ -571,18 +665,38 @@ export class Readable extends EventEmitter {
571
665
  const { done, value } = await this._reader.read();
572
666
  // Check _pushMode again after async read - if push() was called, stop reading
573
667
  if (this._pushMode) {
668
+ if (this._reader) {
669
+ const reader = this._reader;
670
+ this._reader = null;
671
+ try {
672
+ reader.releaseLock();
673
+ }
674
+ catch {
675
+ // Ignore if a read is still pending
676
+ }
677
+ }
574
678
  break;
575
679
  }
576
680
  if (done) {
577
681
  this._ended = true;
578
- this.emit("end");
682
+ this._emitEndOnce();
683
+ if (this._reader) {
684
+ const reader = this._reader;
685
+ this._reader = null;
686
+ try {
687
+ reader.releaseLock();
688
+ }
689
+ catch {
690
+ // Ignore if a read is still pending
691
+ }
692
+ }
579
693
  break;
580
694
  }
581
695
  if (value !== undefined) {
582
696
  // In flowing mode, emit data directly without buffering
583
697
  // Only buffer if not flowing (paused mode)
584
698
  if (this._flowing) {
585
- this.emit("data", value);
699
+ this.emit("data", this._applyEncoding(value));
586
700
  }
587
701
  else {
588
702
  this._buffer.push(value);
@@ -595,6 +709,16 @@ export class Readable extends EventEmitter {
595
709
  }
596
710
  catch (err) {
597
711
  this.emit("error", err);
712
+ if (this._reader) {
713
+ const reader = this._reader;
714
+ this._reader = null;
715
+ try {
716
+ reader.releaseLock();
717
+ }
718
+ catch {
719
+ // Ignore if a read is still pending
720
+ }
721
+ }
598
722
  }
599
723
  finally {
600
724
  this._reading = false;
@@ -602,7 +726,8 @@ export class Readable extends EventEmitter {
602
726
  }
603
727
  /**
604
728
  * Async iterator support
605
- * Uses Web Stream reader for non-push mode, event-based for push mode
729
+ * Uses a unified event-queue iterator with simple backpressure.
730
+ * This matches Node's behavior more closely (iterator drives flowing mode).
606
731
  */
607
732
  async *[Symbol.asyncIterator]() {
608
733
  // First yield any buffered data
@@ -611,117 +736,124 @@ export class Readable extends EventEmitter {
611
736
  if (!this.objectMode) {
612
737
  this._bufferSize -= this._getChunkSize(chunk);
613
738
  }
614
- yield chunk;
739
+ yield this._applyEncoding(chunk);
615
740
  }
616
- // If already ended, we're done
617
741
  if (this._ended) {
618
742
  return;
619
743
  }
620
- // For controllable streams (not created from external Web Stream),
621
- // use event-based iteration since data comes from push() calls
622
- if (!this._webStreamMode) {
623
- // Create a promise-based queue for incoming data
624
- const dataQueue = [];
625
- let resolveNext = null;
626
- let rejectNext = null;
627
- let done = false;
628
- let streamError = null;
629
- let dataQueueIndex = 0;
630
- const dataHandler = (chunk) => {
631
- if (resolveNext) {
632
- resolveNext(chunk);
633
- resolveNext = null;
634
- rejectNext = null;
635
- }
636
- else {
637
- dataQueue.push(chunk);
638
- }
639
- };
640
- const endHandler = () => {
641
- done = true;
642
- if (resolveNext) {
643
- resolveNext(null);
644
- resolveNext = null;
645
- rejectNext = null;
646
- }
647
- };
648
- const errorHandler = (err) => {
649
- done = true;
650
- streamError = err;
651
- if (rejectNext) {
652
- rejectNext(err);
653
- resolveNext = null;
654
- rejectNext = null;
655
- }
656
- };
657
- const closeHandler = () => {
658
- // If stream closed without end event (e.g., after destroy()),
659
- // treat it as done
660
- done = true;
661
- if (resolveNext) {
662
- resolveNext(null);
663
- resolveNext = null;
664
- rejectNext = null;
665
- }
666
- };
667
- this.on("data", dataHandler);
668
- this.on("end", endHandler);
669
- this.on("error", errorHandler);
670
- this.on("close", closeHandler);
671
- try {
672
- // Enter flowing mode
673
- this.resume();
674
- while (!done || dataQueueIndex < dataQueue.length) {
675
- // Check for error before processing
676
- if (streamError) {
677
- throw streamError;
678
- }
679
- if (dataQueueIndex < dataQueue.length) {
680
- const chunk = dataQueue[dataQueueIndex++];
681
- if (dataQueueIndex >= 1024 && dataQueueIndex * 2 >= dataQueue.length) {
682
- dataQueue.splice(0, dataQueueIndex);
683
- dataQueueIndex = 0;
684
- }
685
- yield chunk;
686
- }
687
- else if (!done) {
688
- const chunk = await new Promise((resolve, reject) => {
689
- resolveNext = resolve;
690
- rejectNext = reject;
691
- });
692
- if (chunk !== null) {
693
- yield chunk;
694
- }
695
- }
696
- }
697
- // Check for error after loop
698
- if (streamError) {
699
- throw streamError;
700
- }
744
+ const highWaterMark = this.readableHighWaterMark;
745
+ const lowWaterMark = Math.max(0, Math.floor(highWaterMark / 2));
746
+ const chunkSizeForBackpressure = (chunk) => {
747
+ if (this.objectMode) {
748
+ return 1;
701
749
  }
702
- finally {
703
- this.off("data", dataHandler);
704
- this.off("end", endHandler);
705
- this.off("error", errorHandler);
706
- this.off("close", closeHandler);
750
+ if (chunk instanceof Uint8Array) {
751
+ return chunk.byteLength;
707
752
  }
708
- return;
709
- }
710
- // For Web Stream mode, use the underlying reader
711
- if (!this._reader) {
712
- this._reader = this._stream.getReader();
713
- }
753
+ if (typeof chunk === "string") {
754
+ return chunk.length;
755
+ }
756
+ return 1;
757
+ };
758
+ const dataQueue = [];
759
+ let dataQueueIndex = 0;
760
+ let queuedSize = 0;
761
+ let resolveNext = null;
762
+ let rejectNext = null;
763
+ let done = false;
764
+ let pausedByIterator = false;
765
+ let streamError = null;
766
+ const dataHandler = (chunk) => {
767
+ // data events are already encoding-aware; do not decode again here.
768
+ if (resolveNext) {
769
+ resolveNext(chunk);
770
+ resolveNext = null;
771
+ rejectNext = null;
772
+ }
773
+ else {
774
+ dataQueue.push(chunk);
775
+ }
776
+ queuedSize += chunkSizeForBackpressure(chunk);
777
+ if (!pausedByIterator && queuedSize >= highWaterMark) {
778
+ pausedByIterator = true;
779
+ this.pause();
780
+ }
781
+ };
782
+ const endHandler = () => {
783
+ done = true;
784
+ if (resolveNext) {
785
+ resolveNext(null);
786
+ resolveNext = null;
787
+ rejectNext = null;
788
+ }
789
+ };
790
+ const closeHandler = () => {
791
+ done = true;
792
+ if (resolveNext) {
793
+ resolveNext(null);
794
+ resolveNext = null;
795
+ rejectNext = null;
796
+ }
797
+ };
798
+ const errorHandler = (err) => {
799
+ done = true;
800
+ streamError = err;
801
+ if (rejectNext) {
802
+ rejectNext(err);
803
+ resolveNext = null;
804
+ rejectNext = null;
805
+ }
806
+ };
807
+ this.on("data", dataHandler);
808
+ this.on("end", endHandler);
809
+ this.on("error", errorHandler);
810
+ this.on("close", closeHandler);
714
811
  try {
812
+ // Iterator consumption should drive the stream.
813
+ this.resume();
715
814
  while (true) {
716
- const { done, value } = await this._reader.read();
815
+ if (streamError) {
816
+ throw streamError;
817
+ }
818
+ if (dataQueueIndex < dataQueue.length) {
819
+ const chunk = dataQueue[dataQueueIndex++];
820
+ queuedSize -= chunkSizeForBackpressure(chunk);
821
+ if (dataQueueIndex >= 1024 && dataQueueIndex * 2 >= dataQueue.length) {
822
+ dataQueue.splice(0, dataQueueIndex);
823
+ dataQueueIndex = 0;
824
+ }
825
+ if (pausedByIterator && queuedSize <= lowWaterMark && !done && !this._destroyed) {
826
+ pausedByIterator = false;
827
+ this.resume();
828
+ }
829
+ yield chunk;
830
+ continue;
831
+ }
717
832
  if (done) {
718
833
  break;
719
834
  }
720
- yield value;
835
+ const chunk = await new Promise((resolve, reject) => {
836
+ resolveNext = resolve;
837
+ rejectNext = reject;
838
+ });
839
+ if (chunk !== null) {
840
+ queuedSize -= chunkSizeForBackpressure(chunk);
841
+ if (pausedByIterator && queuedSize <= lowWaterMark && !done && !this._destroyed) {
842
+ pausedByIterator = false;
843
+ this.resume();
844
+ }
845
+ yield chunk;
846
+ }
847
+ }
848
+ if (streamError) {
849
+ throw streamError;
721
850
  }
722
851
  }
723
852
  finally {
724
- this._reader.releaseLock();
853
+ this.off("data", dataHandler);
854
+ this.off("end", endHandler);
855
+ this.off("error", errorHandler);
856
+ this.off("close", closeHandler);
725
857
  }
726
858
  }
727
859
  /**
@@ -940,6 +1072,32 @@ export class Readable extends EventEmitter {
940
1072
  return stream;
941
1073
  }
942
1074
  }
1075
+ function toAsyncIterable(iterable) {
1076
+ if (iterable && typeof iterable[Symbol.asyncIterator] === "function") {
1077
+ return iterable;
1078
+ }
1079
+ return (async function* () {
1080
+ for (const item of iterable) {
1081
+ yield item;
1082
+ }
1083
+ })();
1084
+ }
1085
+ function pumpAsyncIterableToReadable(readable, iterable) {
1086
+ (async () => {
1087
+ try {
1088
+ for await (const chunk of iterable) {
1089
+ if (!readable.push(chunk)) {
1090
+ // Simple backpressure: yield to consumer.
1091
+ await new Promise(resolve => setTimeout(resolve, 0));
1092
+ }
1093
+ }
1094
+ readable.push(null);
1095
+ }
1096
+ catch (err) {
1097
+ readable.destroy(err);
1098
+ }
1099
+ })();
1100
+ }
943
1101
  // =============================================================================
944
1102
  // Writable Stream Wrapper
945
1103
  // =============================================================================
@@ -957,10 +1115,12 @@ export class Writable extends EventEmitter {
957
1115
  this._closed = false;
958
1116
  this._pendingWrites = 0;
959
1117
  this._writableLength = 0;
1118
+ this._needDrain = false;
960
1119
  this._corked = 0;
961
1120
  this._corkedChunks = [];
962
1121
  this._defaultEncoding = "utf8";
963
1122
  this._aborted = false;
1123
+ this._ownsStream = false;
964
1124
  this.objectMode = options?.objectMode ?? false;
965
1125
  this.writableHighWaterMark = options?.highWaterMark ?? 16384;
966
1126
  this.autoDestroy = options?.autoDestroy ?? true;
@@ -976,8 +1136,10 @@ export class Writable extends EventEmitter {
976
1136
  }
977
1137
  if (options?.stream) {
978
1138
  this._stream = options.stream;
1139
+ this._ownsStream = false;
979
1140
  }
980
1141
  else {
1142
+ this._ownsStream = true;
981
1143
  // Create bound references to instance properties/methods for use in WritableStream callbacks
982
1144
  const getWriteFunc = () => this._writeFunc;
983
1145
  const getFinalFunc = () => this._finalFunc;
@@ -1084,25 +1246,37 @@ export class Writable extends EventEmitter {
1084
1246
  this._writableLength += chunkSize;
1085
1247
  return this._writableLength < this.writableHighWaterMark;
1086
1248
  }
1087
- return this._doWrite(chunk, cb);
1249
+ const ok = this._doWrite(chunk, cb);
1250
+ if (!ok) {
1251
+ this._needDrain = true;
1252
+ }
1253
+ return ok;
1088
1254
  }
1089
1255
  _doWrite(chunk, callback) {
1090
1256
  // Track pending writes for writableLength
1091
1257
  const chunkSize = this._getChunkSize(chunk);
1092
1258
  this._pendingWrites++;
1093
1259
  this._writableLength += chunkSize;
1094
- this._getWriter()
1260
+ const writer = this._getWriter();
1261
+ writer
1095
1262
  .write(chunk)
1096
1263
  .then(() => {
1097
1264
  this._pendingWrites--;
1098
1265
  this._writableLength -= chunkSize;
1099
- this.emit("drain");
1266
+ if (this._needDrain && this._writableLength < this.writableHighWaterMark) {
1267
+ this._needDrain = false;
1268
+ this.emit("drain");
1269
+ }
1100
1270
  callback?.(null);
1101
1271
  })
1102
1272
  .catch(err => {
1103
1273
  this._pendingWrites--;
1104
1274
  this._writableLength -= chunkSize;
1105
- this.emit("error", err);
1275
+ // Avoid double-emitting if we're already in an errored/destroyed state.
1276
+ if (!this._destroyed) {
1277
+ this._errored = err;
1278
+ this.emit("error", err);
1279
+ }
1106
1280
  callback?.(err);
1107
1281
  });
1108
1282
  // Return false if we've exceeded high water mark (for backpressure)
@@ -1133,12 +1307,29 @@ export class Writable extends EventEmitter {
1133
1307
  : callback;
1134
1308
  const finish = async () => {
1135
1309
  try {
1310
+ const writer = this._getWriter();
1136
1311
  if (chunk !== undefined) {
1137
- await this._getWriter().write(chunk);
1312
+ await writer.write(chunk);
1313
+ }
1314
+ await writer.close();
1315
+ if (this._writer === writer) {
1316
+ this._writer = null;
1317
+ try {
1318
+ writer.releaseLock();
1319
+ }
1320
+ catch {
1321
+ // Ignore
1322
+ }
1323
+ }
1324
+ // If we own the underlying Web WritableStream, its `close()` handler already
1325
+ // emits finish/close. For external streams, we must emit finish ourselves.
1326
+ if (!this._ownsStream) {
1327
+ this._finished = true;
1328
+ this.emit("finish");
1329
+ if (this.emitClose) {
1330
+ this.emit("close");
1331
+ }
1138
1332
  }
1139
- await this._getWriter().close();
1140
- this._finished = true;
1141
- this.emit("finish");
1142
1333
  if (cb) {
1143
1334
  cb();
1144
1335
  }
@@ -1159,12 +1350,24 @@ export class Writable extends EventEmitter {
1159
1350
  }
1160
1351
  this._destroyed = true;
1161
1352
  this._ended = true;
1162
- if (error) {
1353
+ if (error && !this._errored) {
1163
1354
  this._errored = error;
1164
1355
  this.emit("error", error);
1165
1356
  }
1166
1357
  if (this._writer) {
1167
- this._writer.abort(error).catch(() => { });
1358
+ const writer = this._writer;
1359
+ this._writer = null;
1360
+ writer
1361
+ .abort(error)
1362
+ .catch(() => { })
1363
+ .finally(() => {
1364
+ try {
1365
+ writer.releaseLock();
1366
+ }
1367
+ catch {
1368
+ // Ignore
1369
+ }
1370
+ });
1168
1371
  }
1169
1372
  this._closed = true;
1170
1373
  this.emit("close");
@@ -1315,332 +1518,259 @@ export function normalizeWritable(stream) {
1315
1518
  */
1316
1519
  export class Transform extends EventEmitter {
1317
1520
  /**
1318
- * Push data to the readable side (Node.js compatibility)
1319
- * Can be called from within transform callback
1521
+ * Push data to the readable side (Node.js compatibility).
1522
+ * Intended to be called from within transform/flush.
1320
1523
  */
1321
1524
  push(chunk) {
1322
- if (chunk === null) {
1323
- return false;
1324
- }
1325
- if (this._transformController) {
1326
- // If we're in a transform callback, enqueue directly
1327
- this._transformController.enqueue(chunk);
1328
- }
1329
- else {
1330
- // Otherwise buffer for later
1331
- this._pushBuffer.push(chunk);
1332
- }
1333
- return true;
1525
+ return this._readable.push(chunk);
1334
1526
  }
1335
1527
  constructor(options) {
1336
1528
  super();
1337
- this._ended = false;
1338
1529
  this._destroyed = false;
1530
+ this._ended = false;
1339
1531
  this._errored = false;
1340
- // Buffer for Node.js style push() calls during transform
1341
- this._pushBuffer = [];
1342
- // Controller for enqueueing pushed data (set during transform execution)
1343
- this._transformController = null;
1344
- // Buffer for writes that occur after end() but before writable is closed
1345
- this._pendingEndWrites = [];
1346
- // Whether end() has been called but writable not yet closed
1347
- this._endPending = false;
1348
- // Track if we've already set up data forwarding
1349
1532
  this._dataForwardingSetup = false;
1350
- /** @internal - whether we have a data event consumer */
1351
- this._hasDataConsumer = false;
1352
- /** @internal - whether we're auto-consuming the readable */
1353
- this._readableConsuming = false;
1354
- /** @internal - buffer for auto-consumed data */
1355
- this._autoConsumedBuffer = [];
1356
- this._autoConsumedBufferIndex = 0;
1357
- /** @internal - whether auto-consume has ended */
1358
- this._autoConsumeEnded = false;
1359
- /** @internal - promise that resolves when auto-consume finishes */
1360
- this._autoConsumePromise = null;
1533
+ this._endTimer = null;
1534
+ this._webStream = null;
1535
+ this._sideForwardingCleanup = null;
1361
1536
  this.objectMode = options?.objectMode ?? false;
1362
- const userTransform = options?.transform;
1363
- const userFlush = options?.flush;
1364
- // Create bound references for use in TransformStream callbacks
1365
- const setController = (ctrl) => {
1366
- this._transformController = ctrl;
1367
- };
1368
- const emitEvent = (event, ...args) => {
1369
- if (event === "error") {
1370
- // Only emit error once to prevent duplicate events
1371
- if (this._errored) {
1372
- return false;
1373
- }
1374
- this._errored = true;
1375
- // Also destroy the writable to prevent further writes
1376
- this._writable.destroy(args[0]);
1537
+ this._transformImpl = options?.transform;
1538
+ this._flushImpl = options?.flush;
1539
+ this._readable = new Readable({
1540
+ objectMode: this.objectMode
1541
+ });
1542
+ this._writable = new Writable({
1543
+ objectMode: this.objectMode,
1544
+ write: (chunk, _encoding, callback) => {
1545
+ this._runTransform(chunk)
1546
+ .then(() => callback(null))
1547
+ .catch(err => callback(err));
1548
+ },
1549
+ final: callback => {
1550
+ this._runFlush()
1551
+ .then(() => {
1552
+ this._readable.push(null);
1553
+ callback(null);
1554
+ })
1555
+ .catch(err => callback(err));
1377
1556
  }
1378
- return this.emit(event, ...args);
1379
- };
1380
- const getInstance = () => this;
1381
- // Check if subclass overrides _transform (for Node.js compatibility)
1382
- // We need to check this at runtime since the subclass constructor runs after super()
1383
- const hasSubclassTransform = () => {
1384
- // If userTransform was provided in options, use that
1385
- if (userTransform) {
1386
- return false;
1557
+ });
1558
+ this._setupSideForwarding();
1559
+ }
1560
+ _setupSideForwarding() {
1561
+ if (this._sideForwardingCleanup) {
1562
+ this._sideForwardingCleanup();
1563
+ this._sideForwardingCleanup = null;
1564
+ }
1565
+ const registry = createListenerRegistry();
1566
+ registry.once(this._readable, "end", () => this.emit("end"));
1567
+ registry.add(this._readable, "error", err => this._emitErrorOnce(err));
1568
+ registry.once(this._writable, "finish", () => this.emit("finish"));
1569
+ registry.add(this._writable, "drain", () => this.emit("drain"));
1570
+ registry.add(this._writable, "error", err => this._emitErrorOnce(err));
1571
+ this._sideForwardingCleanup = () => registry.cleanup();
1572
+ }
1573
+ _scheduleEnd() {
1574
+ if (this._destroyed || this._errored) {
1575
+ return;
1576
+ }
1577
+ if (this._writable.writableEnded) {
1578
+ return;
1579
+ }
1580
+ if (this._endTimer) {
1581
+ clearTimeout(this._endTimer);
1582
+ }
1583
+ // Defer closing to allow writes triggered during 'data' callbacks.
1584
+ this._endTimer = setTimeout(() => {
1585
+ this._endTimer = null;
1586
+ if (this._destroyed || this._errored || this._writable.writableEnded) {
1587
+ return;
1387
1588
  }
1388
- // Check if _transform is overridden (not the base class no-op)
1389
- const proto = Object.getPrototypeOf(this);
1390
- return proto._transform !== Transform.prototype._transform;
1391
- };
1392
- const hasSubclassFlush = () => {
1393
- if (userFlush) {
1394
- return false;
1589
+ this._writable.end();
1590
+ }, 0);
1591
+ }
1592
+ _emitErrorOnce(err) {
1593
+ if (this._errored) {
1594
+ return;
1595
+ }
1596
+ this._errored = true;
1597
+ const error = err instanceof Error ? err : new Error(String(err));
1598
+ this.emit("error", error);
1599
+ if (!this._destroyed) {
1600
+ this._destroyed = true;
1601
+ this._readable.destroy(error);
1602
+ this._writable.destroy(error);
1603
+ queueMicrotask(() => this.emit("close"));
1604
+ }
1605
+ }
1606
+ _hasSubclassTransform() {
1607
+ if (this._transformImpl) {
1608
+ return false;
1609
+ }
1610
+ const proto = Object.getPrototypeOf(this);
1611
+ return proto._transform !== Transform.prototype._transform;
1612
+ }
1613
+ _hasSubclassFlush() {
1614
+ if (this._flushImpl) {
1615
+ return false;
1616
+ }
1617
+ const proto = Object.getPrototypeOf(this);
1618
+ return proto._flush !== Transform.prototype._flush;
1619
+ }
1620
+ async _runTransform(chunk) {
1621
+ if (this._destroyed || this._errored) {
1622
+ throw new Error(this._errored ? "Cannot write after stream errored" : "Cannot write after stream destroyed");
1623
+ }
1624
+ try {
1625
+ if (this._hasSubclassTransform()) {
1626
+ await new Promise((resolve, reject) => {
1627
+ this._transform(chunk, "utf8", (err, data) => {
1628
+ if (err) {
1629
+ reject(err);
1630
+ return;
1631
+ }
1632
+ if (data !== undefined) {
1633
+ this.push(data);
1634
+ }
1635
+ resolve();
1636
+ });
1637
+ });
1638
+ return;
1395
1639
  }
1396
- const proto = Object.getPrototypeOf(this);
1397
- return proto._flush !== Transform.prototype._flush;
1398
- };
1399
- this._stream = new TransformStream({
1400
- transform: async (chunk, controller) => {
1401
- // Skip processing if already errored
1402
- if (this._errored) {
1403
- return;
1404
- }
1405
- try {
1406
- // Set controller for push() to use
1407
- setController(controller);
1408
- // Check for subclass _transform override first
1409
- if (hasSubclassTransform()) {
1410
- // Call subclass _transform method (Node.js style)
1411
- // _transform signature is (chunk, encoding, callback)
1412
- await new Promise((resolve, reject) => {
1413
- this._transform(chunk, "utf8", (err, data) => {
1414
- if (err) {
1415
- reject(err);
1416
- }
1417
- else {
1418
- if (data !== undefined) {
1419
- controller.enqueue(data);
1420
- }
1421
- resolve();
1422
- }
1423
- });
1424
- });
1425
- }
1426
- else if (userTransform) {
1427
- const transformParamCount = userTransform.length;
1428
- if (transformParamCount >= 3) {
1429
- // Node.js style: transform(chunk, encoding, callback)
1430
- await new Promise((resolve, reject) => {
1431
- userTransform.call(getInstance(), chunk, "utf8", (err, data) => {
1432
- if (err) {
1433
- reject(err);
1434
- }
1435
- else {
1436
- if (data !== undefined) {
1437
- controller.enqueue(data);
1438
- }
1439
- resolve();
1440
- }
1441
- });
1442
- });
1640
+ const userTransform = this._transformImpl;
1641
+ if (!userTransform) {
1642
+ this.push(chunk);
1643
+ return;
1644
+ }
1645
+ const paramCount = userTransform.length;
1646
+ if (paramCount >= 3) {
1647
+ await new Promise((resolve, reject) => {
1648
+ userTransform.call(this, chunk, "utf8", (err, data) => {
1649
+ if (err) {
1650
+ reject(err);
1651
+ return;
1443
1652
  }
1444
- else if (transformParamCount === 2) {
1445
- await new Promise((resolve, reject) => {
1446
- userTransform.call(getInstance(), chunk, (err, data) => {
1447
- if (err) {
1448
- reject(err);
1449
- }
1450
- else {
1451
- if (data !== undefined) {
1452
- controller.enqueue(data);
1453
- }
1454
- resolve();
1455
- }
1456
- });
1457
- });
1653
+ if (data !== undefined) {
1654
+ this.push(data);
1458
1655
  }
1459
- else {
1460
- // Simple style: transform(chunk) => result
1461
- const result = userTransform.call(getInstance(), chunk);
1462
- if (result && typeof result.then === "function") {
1463
- const awaitedResult = await result;
1464
- if (awaitedResult !== undefined) {
1465
- controller.enqueue(awaitedResult);
1466
- }
1467
- }
1468
- else {
1469
- if (result !== undefined) {
1470
- controller.enqueue(result);
1471
- }
1472
- }
1656
+ resolve();
1657
+ });
1658
+ });
1659
+ return;
1660
+ }
1661
+ if (paramCount === 2) {
1662
+ await new Promise((resolve, reject) => {
1663
+ userTransform.call(this, chunk, (err, data) => {
1664
+ if (err) {
1665
+ reject(err);
1666
+ return;
1473
1667
  }
1474
- }
1475
- else {
1476
- // Default: pass through
1477
- controller.enqueue(chunk);
1478
- }
1479
- }
1480
- catch (err) {
1481
- controller.error(err);
1482
- emitEvent("error", err);
1483
- }
1484
- finally {
1485
- setController(null);
1668
+ if (data !== undefined) {
1669
+ this.push(data);
1670
+ }
1671
+ resolve();
1672
+ });
1673
+ });
1674
+ return;
1675
+ }
1676
+ const result = userTransform.call(this, chunk);
1677
+ if (result && typeof result.then === "function") {
1678
+ const awaited = await result;
1679
+ if (awaited !== undefined) {
1680
+ this.push(awaited);
1486
1681
  }
1487
- },
1488
- flush: async (controller) => {
1489
- try {
1490
- setController(controller);
1491
- // Check for subclass _flush override first
1492
- if (hasSubclassFlush()) {
1493
- await new Promise((resolve, reject) => {
1494
- this._flush((err, data) => {
1495
- if (err) {
1496
- reject(err);
1497
- }
1498
- else {
1499
- if (data !== undefined) {
1500
- controller.enqueue(data);
1501
- }
1502
- resolve();
1503
- }
1504
- });
1505
- });
1506
- }
1507
- else if (userFlush) {
1508
- const flushParamCount = userFlush.length;
1509
- if (flushParamCount >= 1) {
1510
- // Node.js style: flush(callback)
1511
- await new Promise((resolve, reject) => {
1512
- userFlush.call(getInstance(), (err, data) => {
1513
- if (err) {
1514
- reject(err);
1515
- }
1516
- else {
1517
- if (data !== undefined) {
1518
- controller.enqueue(data);
1519
- }
1520
- resolve();
1521
- }
1522
- });
1523
- });
1682
+ return;
1683
+ }
1684
+ if (result !== undefined) {
1685
+ this.push(result);
1686
+ }
1687
+ }
1688
+ catch (err) {
1689
+ this._emitErrorOnce(err);
1690
+ throw err;
1691
+ }
1692
+ }
1693
+ async _runFlush() {
1694
+ if (this._destroyed || this._errored) {
1695
+ return;
1696
+ }
1697
+ try {
1698
+ if (this._hasSubclassFlush()) {
1699
+ await new Promise((resolve, reject) => {
1700
+ this._flush((err, data) => {
1701
+ if (err) {
1702
+ reject(err);
1703
+ return;
1524
1704
  }
1525
- else {
1526
- // Simple style: flush() => result
1527
- const result = userFlush.call(getInstance());
1528
- if (result && typeof result.then === "function") {
1529
- const awaitedResult = await result;
1530
- if (awaitedResult !== undefined && awaitedResult !== null) {
1531
- controller.enqueue(awaitedResult);
1532
- }
1533
- }
1534
- else {
1535
- if (result !== undefined && result !== null) {
1536
- controller.enqueue(result);
1537
- }
1538
- }
1705
+ if (data !== undefined) {
1706
+ this.push(data);
1539
1707
  }
1540
- }
1541
- // No flush defined - nothing to do
1542
- }
1543
- catch (err) {
1544
- controller.error(err);
1545
- emitEvent("error", err);
1546
- }
1547
- finally {
1548
- setController(null);
1549
- }
1708
+ resolve();
1709
+ });
1710
+ });
1711
+ return;
1550
1712
  }
1551
- });
1552
- this._readable = new Readable({
1553
- stream: this._stream.readable,
1554
- objectMode: this.objectMode
1555
- });
1556
- this._writable = new Writable({
1557
- stream: this._stream.writable,
1558
- objectMode: this.objectMode
1559
- });
1560
- // Forward non-data events (data forwarding is lazy to avoid premature flowing)
1561
- this._readable.on("end", () => this.emit("end"));
1562
- // Only forward errors if not already errored (to prevent duplicate events)
1563
- this._readable.on("error", err => {
1564
- if (!this._errored) {
1565
- this._errored = true;
1566
- this.emit("error", err);
1713
+ const userFlush = this._flushImpl;
1714
+ if (!userFlush) {
1715
+ return;
1716
+ }
1717
+ const paramCount = userFlush.length;
1718
+ if (paramCount >= 1) {
1719
+ await new Promise((resolve, reject) => {
1720
+ userFlush.call(this, (err, data) => {
1721
+ if (err) {
1722
+ reject(err);
1723
+ return;
1724
+ }
1725
+ if (data !== undefined) {
1726
+ this.push(data);
1727
+ }
1728
+ resolve();
1729
+ });
1730
+ });
1731
+ return;
1567
1732
  }
1568
- });
1569
- this._writable.on("finish", () => this.emit("finish"));
1570
- this._writable.on("drain", () => this.emit("drain"));
1571
- // Only forward errors if not already errored (to prevent duplicate events)
1572
- this._writable.on("error", err => {
1573
- if (!this._errored) {
1574
- this._errored = true;
1575
- this.emit("error", err);
1733
+ const result = userFlush.call(this);
1734
+ if (result && typeof result.then === "function") {
1735
+ const awaited = await result;
1736
+ if (awaited !== undefined && awaited !== null) {
1737
+ this.push(awaited);
1738
+ }
1739
+ return;
1576
1740
  }
1577
- });
1741
+ if (result !== undefined && result !== null) {
1742
+ this.push(result);
1743
+ }
1744
+ }
1745
+ catch (err) {
1746
+ this._emitErrorOnce(err);
1747
+ throw err;
1748
+ }
1578
1749
  }
1579
1750
  /**
1580
- * Override on to start flowing when data listener is added
1751
+ * Override on() to lazily forward readable 'data' events.
1752
+ * Avoids starting flowing mode unless requested.
1581
1753
  */
1582
1754
  on(event, listener) {
1583
- // Set up data forwarding when first external data listener is added
1584
1755
  if (event === "data" && !this._dataForwardingSetup) {
1585
1756
  this._dataForwardingSetup = true;
1586
- this._readable.on("data", data => this.emit("data", data));
1587
- }
1588
- super.on(event, listener);
1589
- // When data listener is added, mark as having consumer
1590
- // and start the readable in flowing mode
1591
- if (event === "data") {
1592
- this._hasDataConsumer = true;
1593
- this._readable.resume();
1757
+ this._readable.on("data", chunk => this.emit("data", chunk));
1594
1758
  }
1595
- return this;
1759
+ return super.on(event, listener);
1596
1760
  }
1597
1761
  write(chunk, encodingOrCallback, callback) {
1598
1762
  const cb = typeof encodingOrCallback === "function" ? encodingOrCallback : callback;
1599
- if (this._destroyed || this._errored) {
1600
- const err = new Error(this._errored ? "Cannot write after stream errored" : "Cannot write after stream destroyed");
1601
- queueMicrotask(() => this.emit("error", err));
1602
- cb?.(err);
1603
- return false;
1604
- }
1605
- // Ensure readable is being consumed to allow transform to execute
1606
- // This matches Node.js behavior where transform executes immediately on write
1607
- // Only auto-consume if no explicit consumer (data listener or pipe)
1608
- if (!this._readableConsuming && !this._hasDataConsumer) {
1609
- this._readableConsuming = true;
1610
- this._startAutoConsume();
1611
- }
1612
- // If end() was called but writable not yet closed, buffer the write
1613
- // This allows writes during data event handlers to be processed
1614
- if (this._endPending) {
1615
- this._pendingEndWrites.push({ chunk, callback: cb });
1616
- return true;
1763
+ // If end() has been requested, keep the close deferred as long as writes continue.
1764
+ if (this._ended && !this._writable.writableEnded) {
1765
+ this._scheduleEnd();
1617
1766
  }
1618
1767
  return this._writable.write(chunk, cb);
1619
1768
  }
1620
- /** @internal - auto-consume readable to allow transform to execute */
1621
- _startAutoConsume() {
1622
- this._autoConsumePromise = (async () => {
1623
- try {
1624
- for await (const chunk of this._readable) {
1625
- // Buffer the data for later retrieval
1626
- this._autoConsumedBuffer.push(chunk);
1627
- // Also emit data event for listeners
1628
- this.emit("data", chunk);
1629
- }
1630
- this._autoConsumeEnded = true;
1631
- this.emit("end");
1632
- }
1633
- catch (err) {
1634
- this.emit("error", err);
1635
- }
1636
- })();
1637
- }
1638
1769
  end(chunkOrCallback, encodingOrCallback, callback) {
1639
1770
  if (this._ended) {
1640
1771
  return this;
1641
1772
  }
1642
1773
  this._ended = true;
1643
- this._endPending = true;
1644
1774
  const chunk = typeof chunkOrCallback === "function" ? undefined : chunkOrCallback;
1645
1775
  const cb = typeof chunkOrCallback === "function"
1646
1776
  ? chunkOrCallback
@@ -1653,18 +1783,7 @@ export class Transform extends EventEmitter {
1653
1783
  if (chunk !== undefined) {
1654
1784
  this._writable.write(chunk);
1655
1785
  }
1656
- // Use setTimeout(0) instead of queueMicrotask to ensure all transform
1657
- // processing and data events complete before we close the writable.
1658
- // Microtasks run before the TransformStream processes data.
1659
- setTimeout(() => {
1660
- // Process any writes that occurred during data events
1661
- for (const { chunk: pendingChunk, callback } of this._pendingEndWrites) {
1662
- this._writable.write(pendingChunk, callback);
1663
- }
1664
- this._pendingEndWrites = [];
1665
- this._endPending = false;
1666
- this._writable.end();
1667
- }, 0);
1786
+ this._scheduleEnd();
1668
1787
  return this;
1669
1788
  }
1670
1789
  /**
@@ -1674,11 +1793,9 @@ export class Transform extends EventEmitter {
1674
1793
  return this._readable.read(size);
1675
1794
  }
1676
1795
  /**
1677
- * Pipe to another stream (writable, transform, or duplex)
1796
+ * Pipe readable side to destination
1678
1797
  */
1679
1798
  pipe(destination) {
1680
- // Mark as having consumer to prevent auto-consume conflict
1681
- this._hasDataConsumer = true;
1682
1799
  return this._readable.pipe(destination);
1683
1800
  }
1684
1801
  /**
@@ -1716,6 +1833,10 @@ export class Transform extends EventEmitter {
1716
1833
  return;
1717
1834
  }
1718
1835
  this._destroyed = true;
1836
+ if (this._sideForwardingCleanup) {
1837
+ this._sideForwardingCleanup();
1838
+ this._sideForwardingCleanup = null;
1839
+ }
1719
1840
  this._readable.destroy(error);
1720
1841
  this._writable.destroy(error);
1721
1842
  queueMicrotask(() => this.emit("close"));
@@ -1724,7 +1845,44 @@ export class Transform extends EventEmitter {
1724
1845
  * Get the underlying Web TransformStream
1725
1846
  */
1726
1847
  get webStream() {
1727
- return this._stream;
1848
+ if (this._webStream) {
1849
+ return this._webStream;
1850
+ }
1851
+ // Web Streams interop layer.
1852
+ const iterator = this[Symbol.asyncIterator]();
1853
+ const readable = new ReadableStream({
1854
+ pull: async (controller) => {
1855
+ const { done, value } = await iterator.next();
1856
+ if (done) {
1857
+ controller.close();
1858
+ return;
1859
+ }
1860
+ controller.enqueue(value);
1861
+ },
1862
+ cancel: reason => {
1863
+ this.destroy(reason instanceof Error ? reason : new Error(String(reason)));
1864
+ }
1865
+ });
1866
+ const writable = new WritableStream({
1867
+ write: chunk => new Promise((resolve, reject) => {
1868
+ this.write(chunk, err => {
1869
+ if (err) {
1870
+ reject(err);
1871
+ }
1872
+ else {
1873
+ resolve();
1874
+ }
1875
+ });
1876
+ }),
1877
+ close: () => new Promise(resolve => {
1878
+ this.end(() => resolve());
1879
+ }),
1880
+ abort: reason => {
1881
+ this.destroy(reason instanceof Error ? reason : new Error(String(reason)));
1882
+ }
1883
+ });
1884
+ this._webStream = { readable, writable };
1885
+ return this._webStream;
1728
1886
  }
1729
1887
  get readable() {
1730
1888
  return this._readable.readable;
@@ -1766,19 +1924,6 @@ export class Transform extends EventEmitter {
1766
1924
  * Async iterator support
1767
1925
  */
1768
1926
  async *[Symbol.asyncIterator]() {
1769
- // If auto-consume is running, wait for it to finish and use its buffer
1770
- if (this._autoConsumePromise) {
1771
- await this._autoConsumePromise;
1772
- // Yield all buffered data
1773
- while (this._autoConsumedBufferIndex < this._autoConsumedBuffer.length) {
1774
- yield this._autoConsumedBuffer[this._autoConsumedBufferIndex++];
1775
- }
1776
- // Reset when drained to avoid prefix growth
1777
- this._autoConsumedBuffer.length = 0;
1778
- this._autoConsumedBufferIndex = 0;
1779
- return;
1780
- }
1781
- // Otherwise delegate to readable's iterator
1782
1927
  yield* this._readable[Symbol.asyncIterator]();
1783
1928
  }
1784
1929
  // =========================================================================
@@ -1789,23 +1934,18 @@ export class Transform extends EventEmitter {
1789
1934
  */
1790
1935
  static fromWeb(webStream, options) {
1791
1936
  const transform = new Transform(options);
1792
- // Connect the web stream - set the internal _stream property
1793
- transform._stream = webStream;
1937
+ transform._webStream = webStream;
1794
1938
  // Replace internal streams with the ones from the web stream
1795
1939
  const newReadable = Readable.fromWeb(webStream.readable, { objectMode: options?.objectMode });
1796
1940
  const newWritable = Writable.fromWeb(webStream.writable, { objectMode: options?.objectMode });
1797
- // Remove old event listeners before replacing
1798
- transform._readable.removeAllListeners();
1799
- transform._writable.removeAllListeners();
1941
+ if (transform._sideForwardingCleanup) {
1942
+ transform._sideForwardingCleanup();
1943
+ transform._sideForwardingCleanup = null;
1944
+ }
1800
1945
  transform._readable = newReadable;
1801
1946
  transform._writable = newWritable;
1802
- // Re-connect event forwarding
1803
- newReadable.on("data", (data) => transform.emit("data", data));
1804
- newReadable.on("end", () => transform.emit("end"));
1805
- newReadable.on("error", (err) => transform.emit("error", err));
1806
- newWritable.on("finish", () => transform.emit("finish"));
1807
- newWritable.on("drain", () => transform.emit("drain"));
1808
- newWritable.on("error", (err) => transform.emit("error", err));
1947
+ // Re-connect event forwarding (data forwarding remains lazy via Transform.on)
1948
+ transform._setupSideForwarding();
1809
1949
  return transform;
1810
1950
  }
1811
1951
  /**
@@ -1861,7 +2001,13 @@ export class Duplex extends EventEmitter {
1861
2001
  callback();
1862
2002
  }
1863
2003
  });
1864
- readable.on("error", err => duplex.emit("error", err));
2004
+ const onError = (err) => {
2005
+ duplex.emit("error", err);
2006
+ };
2007
+ const cleanupError = addEmitterListener(readable, "error", onError);
2008
+ addEmitterListener(readable, "end", cleanupError, { once: true });
2009
+ addEmitterListener(readable, "close", cleanupError, { once: true });
2010
+ addEmitterListener(sink, "finish", cleanupError, { once: true });
1865
2011
  readable.pipe(sink);
1866
2012
  };
1867
2013
  // If it has readable and/or writable properties
@@ -1869,21 +2015,25 @@ export class Duplex extends EventEmitter {
1869
2015
  source !== null &&
1870
2016
  "readable" in source &&
1871
2017
  "writable" in source) {
1872
- const duplex = new Duplex();
1873
2018
  const pair = source;
1874
- if (pair.readable) {
1875
- forwardReadableToDuplex(pair.readable, duplex);
1876
- }
1877
- if (pair.writable) {
1878
- return new Duplex({
1879
- objectMode: duplex.writableObjectMode,
1880
- write(chunk, encoding, callback) {
2019
+ // Create one duplex that can bridge both sides.
2020
+ // (Previous behavior returned a new writable-only Duplex and dropped the readable side.)
2021
+ const duplex = new Duplex({
2022
+ readableObjectMode: pair.readable?.readableObjectMode,
2023
+ writableObjectMode: pair.writable?.writableObjectMode,
2024
+ write: pair.writable
2025
+ ? (chunk, encoding, callback) => {
1881
2026
  pair.writable.write(chunk, encoding, callback);
1882
- },
1883
- final(callback) {
2027
+ }
2028
+ : undefined,
2029
+ final: pair.writable
2030
+ ? callback => {
1884
2031
  pair.writable.end(callback);
1885
2032
  }
1886
- });
2033
+ : undefined
2034
+ });
2035
+ if (pair.readable) {
2036
+ forwardReadableToDuplex(pair.readable, duplex);
1887
2037
  }
1888
2038
  return duplex;
1889
2039
  }
@@ -1921,9 +2071,22 @@ export class Duplex extends EventEmitter {
1921
2071
  */
1922
2072
  static fromWeb(pair, options) {
1923
2073
  const duplex = new Duplex(options);
1924
- // Replace internal streams
1925
- duplex._readable = new Readable({ stream: pair.readable });
1926
- duplex._writable = new Writable({ stream: pair.writable });
2074
+ const newReadable = new Readable({
2075
+ stream: pair.readable,
2076
+ objectMode: duplex.readableObjectMode
2077
+ });
2078
+ const newWritable = new Writable({
2079
+ stream: pair.writable,
2080
+ objectMode: duplex.writableObjectMode
2081
+ });
2082
+ if (duplex._sideForwardingCleanup) {
2083
+ duplex._sideForwardingCleanup();
2084
+ duplex._sideForwardingCleanup = null;
2085
+ }
2086
+ duplex._readable = newReadable;
2087
+ duplex._writable = newWritable;
2088
+ // Re-wire event forwarding (data forwarding remains lazy via Duplex.on)
2089
+ duplex._setupSideForwarding();
1927
2090
  return duplex;
1928
2091
  }
1929
2092
  /**
@@ -1939,6 +2102,7 @@ export class Duplex extends EventEmitter {
1939
2102
  super();
1940
2103
  // Track if we've already set up data forwarding
1941
2104
  this._dataForwardingSetup = false;
2105
+ this._sideForwardingCleanup = null;
1942
2106
  this.allowHalfOpen = options?.allowHalfOpen ?? true;
1943
2107
  // Support shorthand objectMode option
1944
2108
  const objectMode = options?.objectMode ?? false;
@@ -1955,23 +2119,31 @@ export class Duplex extends EventEmitter {
1955
2119
  write: options?.write?.bind(this),
1956
2120
  final: options?.final?.bind(this)
1957
2121
  });
2122
+ this._setupSideForwarding();
2123
+ }
2124
+ _setupSideForwarding() {
2125
+ if (this._sideForwardingCleanup) {
2126
+ this._sideForwardingCleanup();
2127
+ this._sideForwardingCleanup = null;
2128
+ }
2129
+ const registry = createListenerRegistry();
1958
2130
  // Forward non-data events (data forwarding is lazy to avoid premature flowing)
1959
- this._readable.on("end", () => {
2131
+ registry.once(this._readable, "end", () => {
1960
2132
  this.emit("end");
1961
- // If not allowHalfOpen, end the writable side too
1962
2133
  if (!this.allowHalfOpen) {
1963
2134
  this._writable.end();
1964
2135
  }
1965
2136
  });
1966
- this._readable.on("error", err => this.emit("error", err));
1967
- this._writable.on("finish", () => this.emit("finish"));
1968
- this._writable.on("drain", () => this.emit("drain"));
1969
- this._writable.on("close", () => {
1970
- // If not allowHalfOpen, destroy the readable side too
2137
+ registry.add(this._readable, "error", err => this.emit("error", err));
2138
+ registry.add(this._writable, "error", err => this.emit("error", err));
2139
+ registry.once(this._writable, "finish", () => this.emit("finish"));
2140
+ registry.add(this._writable, "drain", () => this.emit("drain"));
2141
+ registry.once(this._writable, "close", () => {
1971
2142
  if (!this.allowHalfOpen && !this._readable.destroyed) {
1972
2143
  this._readable.destroy();
1973
2144
  }
1974
2145
  });
2146
+ this._sideForwardingCleanup = () => registry.cleanup();
1975
2147
  }
1976
2148
  /**
1977
2149
  * Override on() to set up data forwarding lazily
@@ -2090,6 +2262,10 @@ export class Duplex extends EventEmitter {
2090
2262
  * Destroy both sides
2091
2263
  */
2092
2264
  destroy(error) {
2265
+ if (this._sideForwardingCleanup) {
2266
+ this._sideForwardingCleanup();
2267
+ this._sideForwardingCleanup = null;
2268
+ }
2093
2269
  this._readable.destroy(error);
2094
2270
  this._writable.destroy(error);
2095
2271
  return this;
@@ -2230,21 +2406,8 @@ export class Collector extends Writable {
2230
2406
  // =============================================================================
2231
2407
  // PullStream / BufferedStream / DataChunk helpers
2232
2408
  // =============================================================================
2233
- export class PullStream extends StandalonePullStream {
2234
- // Keep constructor signature aligned with streams.browser.ts public API
2235
- constructor(options) {
2236
- super(options);
2237
- }
2238
- }
2239
- export class StringChunk extends StandaloneStringChunk {
2240
- }
2241
- export class BufferChunk extends StandaloneBufferChunk {
2242
- }
2243
- export class BufferedStream extends StandaloneBufferedStream {
2244
- constructor(options) {
2245
- super(options);
2246
- }
2247
- }
2409
+ // Standalone cross-platform helpers
2410
+ export { PullStream, BufferedStream, StringChunk, BufferChunk };
2248
2411
  // =============================================================================
2249
2412
  // Stream Creation Functions
2250
2413
  // =============================================================================
@@ -2252,36 +2415,16 @@ export class BufferedStream extends StandaloneBufferedStream {
2252
2415
  * Create a readable stream with custom read implementation
2253
2416
  */
2254
2417
  export function createReadable(options) {
2255
- const readable = new Readable(options);
2256
- // Override read behavior if provided
2257
- if (options?.read) {
2258
- const originalRead = readable.read.bind(readable);
2259
- readable.read = function (size) {
2260
- options.read(size ?? 16384);
2261
- return originalRead(size);
2262
- };
2263
- }
2264
- return readable;
2418
+ // Readable already supports Node-style `read()` via the constructor option.
2419
+ // Keep this helper minimal to avoid accidental double-read behavior.
2420
+ return new Readable(options);
2265
2421
  }
2266
2422
  /**
2267
2423
  * Create a readable stream from an async iterable
2268
2424
  */
2269
2425
  export function createReadableFromAsyncIterable(iterable, options) {
2270
2426
  const readable = new Readable({ ...options, objectMode: options?.objectMode ?? true });
2271
- (async () => {
2272
- try {
2273
- for await (const chunk of iterable) {
2274
- if (!readable.push(chunk)) {
2275
- // Backpressure - wait a bit
2276
- await new Promise(resolve => setTimeout(resolve, 0));
2277
- }
2278
- }
2279
- readable.push(null);
2280
- }
2281
- catch (err) {
2282
- readable.destroy(err);
2283
- }
2284
- })();
2427
+ pumpAsyncIterableToReadable(readable, iterable);
2285
2428
  return readable;
2286
2429
  }
2287
2430
  /**
@@ -2310,38 +2453,8 @@ export function createReadableFromArray(data, options) {
2310
2453
  * Create a writable stream with custom write implementation
2311
2454
  */
2312
2455
  export function createWritable(options) {
2313
- // Create a custom WritableStream with user's handlers
2314
- const stream = new WritableStream({
2315
- write: async (chunk) => {
2316
- if (options?.write) {
2317
- return new Promise((resolve, reject) => {
2318
- options.write(chunk, "utf8", err => {
2319
- if (err) {
2320
- reject(err);
2321
- }
2322
- else {
2323
- resolve();
2324
- }
2325
- });
2326
- });
2327
- }
2328
- },
2329
- close: async () => {
2330
- if (options?.final) {
2331
- return new Promise((resolve, reject) => {
2332
- options.final(err => {
2333
- if (err) {
2334
- reject(err);
2335
- }
2336
- else {
2337
- resolve();
2338
- }
2339
- });
2340
- });
2341
- }
2342
- }
2343
- });
2344
- return new Writable({ ...options, stream });
2456
+ // Writable already supports Node-style `write()` / `final()` via the constructor.
2457
+ return new Writable(options);
2345
2458
  }
2346
2459
  /**
2347
2460
  * Create a transform stream from a transform function
@@ -2377,20 +2490,6 @@ export function createPullStream(options) {
2377
2490
  export function createBufferedStream(options) {
2378
2491
  return new BufferedStream(options);
2379
2492
  }
2380
- const isReadableStream = (value) => !!value && typeof value === "object" && typeof value.getReader === "function";
2381
- const isAsyncIterable = (value) => {
2382
- if (!value || (typeof value !== "object" && typeof value !== "function")) {
2383
- return false;
2384
- }
2385
- return typeof value[Symbol.asyncIterator] === "function";
2386
- };
2387
- const isWritableStream = (value) => !!value && typeof value === "object" && typeof value.getWriter === "function";
2388
- const isTransformStream = (value) => !!value &&
2389
- typeof value === "object" &&
2390
- !!value.readable &&
2391
- !!value.writable &&
2392
- isReadableStream(value.readable) &&
2393
- isWritableStream(value.writable);
2394
2493
  const isPipelineOptions = (value) => {
2395
2494
  if (!value || typeof value !== "object") {
2396
2495
  return false;
@@ -2476,11 +2575,17 @@ export function pipeline(...args) {
2476
2575
  const transforms = normalized.slice(1, -1);
2477
2576
  let completed = false;
2478
2577
  const allStreams = [source, ...transforms, destination];
2479
- const cleanup = (error) => {
2578
+ const registry = createListenerRegistry();
2579
+ let onAbort;
2580
+ const cleanupWithSignal = (error) => {
2480
2581
  if (completed) {
2481
2582
  return;
2482
2583
  }
2483
2584
  completed = true;
2585
+ registry.cleanup();
2586
+ if (onAbort && options.signal) {
2587
+ options.signal.removeEventListener("abort", onAbort);
2588
+ }
2484
2589
  // Destroy all streams on error
2485
2590
  if (error) {
2486
2591
  for (const stream of allStreams) {
@@ -2497,12 +2602,11 @@ export function pipeline(...args) {
2497
2602
  // Handle abort signal
2498
2603
  if (options.signal) {
2499
2604
  if (options.signal.aborted) {
2500
- cleanup(new Error("Pipeline aborted"));
2605
+ cleanupWithSignal(new Error("Pipeline aborted"));
2501
2606
  return;
2502
2607
  }
2503
- options.signal.addEventListener("abort", () => {
2504
- cleanup(new Error("Pipeline aborted"));
2505
- });
2608
+ onAbort = () => cleanupWithSignal(new Error("Pipeline aborted"));
2609
+ options.signal.addEventListener("abort", onAbort);
2506
2610
  }
2507
2611
  // Chain the streams
2508
2612
  let current = source;
@@ -2516,13 +2620,35 @@ export function pipeline(...args) {
2516
2620
  }
2517
2621
  else {
2518
2622
  // Don't end destination
2519
- current.on("data", chunk => destination.write(chunk));
2623
+ let paused = false;
2624
+ let waitingForDrain = false;
2625
+ const onDrain = () => {
2626
+ waitingForDrain = false;
2627
+ if (paused && typeof current.resume === "function") {
2628
+ paused = false;
2629
+ current.resume();
2630
+ }
2631
+ };
2632
+ const onData = (chunk) => {
2633
+ const ok = destination.write(chunk);
2634
+ if (!ok && !waitingForDrain) {
2635
+ waitingForDrain = true;
2636
+ if (!paused && typeof current.pause === "function") {
2637
+ paused = true;
2638
+ current.pause();
2639
+ }
2640
+ registry.once(destination, "drain", onDrain);
2641
+ }
2642
+ };
2643
+ const onEnd = () => cleanupWithSignal();
2644
+ registry.add(current, "data", onData);
2645
+ registry.once(current, "end", onEnd);
2520
2646
  }
2521
2647
  // Handle completion
2522
- destination.on("finish", () => cleanup());
2648
+ registry.once(destination, "finish", () => cleanupWithSignal());
2523
2649
  // Handle errors on all streams
2524
2650
  for (const stream of allStreams) {
2525
- stream.on("error", (err) => cleanup(err));
2651
+ registry.once(stream, "error", (err) => cleanupWithSignal(err));
2526
2652
  }
2527
2653
  });
2528
2654
  // If callback provided, use it
@@ -2562,11 +2688,20 @@ export function finished(stream, optionsOrCallback, callback) {
2562
2688
  const promise = new Promise((resolve, reject) => {
2563
2689
  const normalizedStream = toBrowserPipelineStream(stream);
2564
2690
  let resolved = false;
2691
+ const registry = createListenerRegistry();
2692
+ let onAbort;
2693
+ const cleanup = () => {
2694
+ registry.cleanup();
2695
+ if (onAbort && options.signal) {
2696
+ options.signal.removeEventListener("abort", onAbort);
2697
+ }
2698
+ };
2565
2699
  const done = (err) => {
2566
2700
  if (resolved) {
2567
2701
  return;
2568
2702
  }
2569
2703
  resolved = true;
2704
+ cleanup();
2570
2705
  if (err && !options.error) {
2571
2706
  reject(err);
2572
2707
  }
@@ -2580,9 +2715,8 @@ export function finished(stream, optionsOrCallback, callback) {
2580
2715
  done(new Error("Aborted"));
2581
2716
  return;
2582
2717
  }
2583
- options.signal.addEventListener("abort", () => {
2584
- done(new Error("Aborted"));
2585
- });
2718
+ onAbort = () => done(new Error("Aborted"));
2719
+ options.signal.addEventListener("abort", onAbort);
2586
2720
  }
2587
2721
  const checkReadable = options.readable !== false;
2588
2722
  const checkWritable = options.writable !== false;
@@ -2597,13 +2731,13 @@ export function finished(stream, optionsOrCallback, callback) {
2597
2731
  }
2598
2732
  // Listen for events
2599
2733
  if (checkWritable) {
2600
- normalizedStream.on("finish", () => done());
2734
+ registry.once(normalizedStream, "finish", () => done());
2601
2735
  }
2602
2736
  if (checkReadable) {
2603
- normalizedStream.on("end", () => done());
2737
+ registry.once(normalizedStream, "end", () => done());
2604
2738
  }
2605
- normalizedStream.on("error", (err) => done(err));
2606
- normalizedStream.on("close", () => done());
2739
+ registry.once(normalizedStream, "error", (err) => done(err));
2740
+ registry.once(normalizedStream, "close", () => done());
2607
2741
  });
2608
2742
  // If callback provided, use it
2609
2743
  if (cb) {
@@ -2625,38 +2759,8 @@ export async function streamToPromise(stream) {
2625
2759
  * (Browser equivalent of Node.js streamToBuffer)
2626
2760
  */
2627
2761
  export async function streamToUint8Array(stream) {
2628
- let iterable;
2629
- if (isReadableStream(stream)) {
2630
- iterable = Readable.fromWeb(stream);
2631
- }
2632
- else if (isAsyncIterable(stream)) {
2633
- iterable = stream;
2634
- }
2635
- else {
2636
- throw new Error("streamToUint8Array: unsupported stream type");
2637
- }
2638
- const chunks = [];
2639
- let totalLength = 0;
2640
- for await (const chunk of iterable) {
2641
- chunks.push(chunk);
2642
- totalLength += chunk.length;
2643
- }
2644
- // Fast paths
2645
- const len = chunks.length;
2646
- if (len === 0) {
2647
- return new Uint8Array(0);
2648
- }
2649
- if (len === 1) {
2650
- return chunks[0];
2651
- }
2652
- // Use precalculated total length
2653
- const result = new Uint8Array(totalLength);
2654
- let offset = 0;
2655
- for (let i = 0; i < len; i++) {
2656
- result.set(chunks[i], offset);
2657
- offset += chunks[i].length;
2658
- }
2659
- return result;
2762
+ const [chunks, totalLength] = await collectStreamChunks(stream);
2763
+ return concatUint8Arrays(chunks, totalLength);
2660
2764
  }
2661
2765
  /**
2662
2766
  * Alias for streamToUint8Array (Node.js compatibility)
@@ -2666,23 +2770,16 @@ export const streamToBuffer = streamToUint8Array;
2666
2770
  * Collect all data from a readable stream into a string
2667
2771
  */
2668
2772
  export async function streamToString(stream, encoding) {
2669
- const buffer = await streamToUint8Array(stream);
2670
- return getTextDecoder(encoding).decode(buffer);
2773
+ const [chunks, totalLength] = await collectStreamChunks(stream);
2774
+ const combined = concatUint8Arrays(chunks, totalLength);
2775
+ const decoder = encoding ? getTextDecoder(encoding) : textDecoder;
2776
+ return decoder.decode(combined);
2671
2777
  }
2672
2778
  /**
2673
2779
  * Drain a stream (consume all data without processing)
2674
2780
  */
2675
2781
  export async function drainStream(stream) {
2676
- let iterable;
2677
- if (isReadableStream(stream)) {
2678
- iterable = Readable.fromWeb(stream);
2679
- }
2680
- else if (isAsyncIterable(stream)) {
2681
- iterable = stream;
2682
- }
2683
- else {
2684
- throw new Error("drainStream: unsupported stream type");
2685
- }
2782
+ const iterable = toReadableAsyncIterable(stream, "drainStream");
2686
2783
  for await (const _chunk of iterable) {
2687
2784
  // Consume data
2688
2785
  }
@@ -2755,14 +2852,19 @@ export function addAbortSignal(signal, stream) {
2755
2852
  stream.destroy(new Error("Aborted"));
2756
2853
  return stream;
2757
2854
  }
2855
+ const cleanup = () => {
2856
+ signal.removeEventListener("abort", onAbort);
2857
+ removeEmitterListener(stream, "close", onClose);
2858
+ };
2758
2859
  const onAbort = () => {
2860
+ cleanup();
2759
2861
  stream.destroy(new Error("Aborted"));
2760
2862
  };
2761
- signal.addEventListener("abort", onAbort, { once: true });
2762
- // Clean up when stream is destroyed
2763
- stream.on("close", () => {
2764
- signal.removeEventListener("abort", onAbort);
2765
- });
2863
+ const onClose = () => {
2864
+ cleanup();
2865
+ };
2866
+ signal.addEventListener("abort", onAbort);
2867
+ addEmitterListener(stream, "close", onClose, { once: true });
2766
2868
  return stream;
2767
2869
  }
2768
2870
  /**
@@ -2771,60 +2873,68 @@ export function addAbortSignal(signal, stream) {
2771
2873
  export function createDuplex(options) {
2772
2874
  const readableObjectMode = options?.readableObjectMode ?? options?.objectMode;
2773
2875
  const writableObjectMode = options?.writableObjectMode ?? options?.objectMode;
2876
+ const underlyingWritable = options?.writable;
2774
2877
  const duplex = new Duplex({
2775
2878
  allowHalfOpen: options?.allowHalfOpen,
2776
2879
  readableHighWaterMark: options?.readableHighWaterMark,
2777
2880
  writableHighWaterMark: options?.writableHighWaterMark,
2778
2881
  readableObjectMode,
2779
- writableObjectMode
2882
+ writableObjectMode,
2883
+ read: options?.read,
2884
+ write: options?.write ??
2885
+ (underlyingWritable
2886
+ ? (chunk, encoding, callback) => {
2887
+ if (typeof underlyingWritable.write === "function") {
2888
+ underlyingWritable.write(chunk, encoding, callback);
2889
+ return;
2890
+ }
2891
+ // Best-effort sync sink
2892
+ try {
2893
+ underlyingWritable.write?.(chunk);
2894
+ callback(null);
2895
+ }
2896
+ catch (err) {
2897
+ callback(err);
2898
+ }
2899
+ }
2900
+ : undefined),
2901
+ final: options?.final ??
2902
+ (underlyingWritable
2903
+ ? (callback) => {
2904
+ if (typeof underlyingWritable.end === "function") {
2905
+ underlyingWritable.end((err) => callback(err ?? null));
2906
+ }
2907
+ else {
2908
+ underlyingWritable.end?.();
2909
+ callback(null);
2910
+ }
2911
+ }
2912
+ : undefined)
2780
2913
  });
2781
- // If custom readable/writable provided, pipe them
2914
+ // If an underlying readable is provided, forward it into the duplex readable side.
2782
2915
  if (options?.readable) {
2783
2916
  const readable = options.readable;
2784
- readable.on?.("data", (chunk) => duplex.push(chunk));
2785
- readable.on?.("end", () => duplex.push(null));
2786
- readable.on?.("error", (err) => duplex.destroy(err));
2787
- }
2788
- if (options?.writable) {
2789
- const writable = options.writable;
2790
- duplex.on("data", (chunk) => writable.write?.(chunk));
2791
- duplex.on("finish", () => writable.end?.());
2792
- }
2793
- // If custom read/write/final provided, override methods
2794
- if (options?.write) {
2795
- const _originalWrite = duplex.write.bind(duplex); // Keep bound reference for potential future use
2796
- duplex.write = function (chunk, encodingOrCallback, callback) {
2797
- const encoding = typeof encodingOrCallback === "string" ? encodingOrCallback : "utf8";
2798
- const cb = typeof encodingOrCallback === "function" ? encodingOrCallback : (callback ?? (() => { }));
2799
- options.write.call(duplex, chunk, encoding, cb);
2800
- return true;
2801
- };
2802
- }
2803
- if (options?.final) {
2804
- const originalEnd = duplex.end.bind(duplex);
2805
- duplex.end = function (chunkOrCallback, encodingOrCallback, callback) {
2806
- const cb = typeof chunkOrCallback === "function"
2807
- ? chunkOrCallback
2808
- : typeof encodingOrCallback === "function"
2809
- ? encodingOrCallback
2810
- : (callback ?? (() => { }));
2811
- if (chunkOrCallback !== undefined && typeof chunkOrCallback !== "function") {
2812
- duplex.write(chunkOrCallback);
2813
- }
2814
- // Call custom final handler
2815
- options.final.call(duplex, (err) => {
2816
- if (err) {
2817
- duplex.emit("error", err);
2818
- }
2819
- else {
2820
- duplex.emit("finish");
2821
- }
2822
- // Call original end to properly close writable side
2823
- originalEnd();
2824
- cb();
2825
- });
2826
- return duplex;
2827
- };
2917
+ const sink = new Writable({
2918
+ objectMode: duplex.readableObjectMode,
2919
+ write(chunk, _encoding, callback) {
2920
+ duplex.push(chunk);
2921
+ callback(null);
2922
+ },
2923
+ final(callback) {
2924
+ duplex.push(null);
2925
+ callback(null);
2926
+ }
2927
+ });
2928
+ if (typeof readable?.on === "function") {
2929
+ const onError = (err) => {
2930
+ duplex.destroy(err);
2931
+ };
2932
+ const cleanupError = addEmitterListener(readable, "error", onError);
2933
+ addEmitterListener(readable, "end", cleanupError, { once: true });
2934
+ addEmitterListener(readable, "close", cleanupError, { once: true });
2935
+ addEmitterListener(sink, "finish", cleanupError, { once: true });
2936
+ }
2937
+ readable.pipe?.(sink);
2828
2938
  }
2829
2939
  if (options?.destroy) {
2830
2940
  const originalDestroy = duplex.destroy.bind(duplex);
@@ -2848,20 +2958,7 @@ export function createDuplex(options) {
2848
2958
  */
2849
2959
  export function createReadableFromGenerator(generator, options) {
2850
2960
  const readable = new Readable({ ...options, objectMode: options?.objectMode ?? true });
2851
- (async () => {
2852
- try {
2853
- for await (const chunk of generator()) {
2854
- if (!readable.push(chunk)) {
2855
- // Backpressure
2856
- await new Promise(resolve => setTimeout(resolve, 0));
2857
- }
2858
- }
2859
- readable.push(null);
2860
- }
2861
- catch (err) {
2862
- readable.destroy(err);
2863
- }
2864
- })();
2961
+ pumpAsyncIterableToReadable(readable, generator());
2865
2962
  return readable;
2866
2963
  }
2867
2964
  /**
@@ -2891,8 +2988,8 @@ export function compose(...transforms) {
2891
2988
  transform: chunk => chunk
2892
2989
  });
2893
2990
  }
2894
- const isNativeTransform = (stream) => stream instanceof Transform;
2895
- if (len === 1 && isNativeTransform(transforms[0])) {
2991
+ // Preserve identity: compose(single) returns the same transform.
2992
+ if (len === 1) {
2896
2993
  return transforms[0];
2897
2994
  }
2898
2995
  // Chain the transforms: first → second → ... → last
@@ -2902,74 +2999,118 @@ export function compose(...transforms) {
2902
2999
  for (let i = 0; i < len - 1; i++) {
2903
3000
  transforms[i].pipe(transforms[i + 1]);
2904
3001
  }
2905
- class ComposedTransform extends Transform {
2906
- constructor() {
2907
- super(...arguments);
2908
- this._dataForwarding = false;
2909
- this._endForwarding = false;
3002
+ // A lightweight Transform wrapper that delegates:
3003
+ // - writable side to `first`
3004
+ // - readable side to `last`
3005
+ // It forwards relevant events lazily to avoid per-chunk overhead when unused.
3006
+ const composed = new Transform({
3007
+ objectMode: first?.objectMode ?? true,
3008
+ transform: chunk => chunk
3009
+ });
3010
+ const registry = createListenerRegistry();
3011
+ // Always forward errors; they are critical for pipeline semantics.
3012
+ for (const t of transforms) {
3013
+ registry.add(t, "error", (err) => composed.emit("error", err));
3014
+ }
3015
+ // Forward writable-side backpressure/completion events from `first`.
3016
+ registry.add(first, "drain", () => composed.emit("drain"));
3017
+ registry.once(first, "finish", () => composed.emit("finish"));
3018
+ // Forward readable-side events from `last` lazily.
3019
+ let forwardData = false;
3020
+ let forwardEnd = false;
3021
+ let forwardReadable = false;
3022
+ const ensureDataForwarding = () => {
3023
+ if (forwardData) {
3024
+ return;
2910
3025
  }
2911
- on(event, listener) {
2912
- if (event === "data" && !this._dataForwarding) {
2913
- this._dataForwarding = true;
2914
- last.on("data", (chunk) => this.emit("data", chunk));
2915
- }
2916
- if (event === "end" && !this._endForwarding) {
2917
- this._endForwarding = true;
2918
- last.on("end", () => this.emit("end"));
2919
- }
2920
- return super.on(event, listener);
3026
+ forwardData = true;
3027
+ registry.add(last, "data", (chunk) => composed.emit("data", chunk));
3028
+ };
3029
+ const ensureEndForwarding = () => {
3030
+ if (forwardEnd) {
3031
+ return;
2921
3032
  }
2922
- write(chunk, encodingOrCallback, callback) {
2923
- if (typeof encodingOrCallback === "function") {
2924
- return first.write(chunk, encodingOrCallback);
2925
- }
2926
- return first.write(chunk, encodingOrCallback, callback);
3033
+ forwardEnd = true;
3034
+ registry.once(last, "end", () => composed.emit("end"));
3035
+ };
3036
+ const ensureReadableForwarding = () => {
3037
+ if (forwardReadable) {
3038
+ return;
2927
3039
  }
2928
- end(chunkOrCallback, encodingOrCallback, callback) {
2929
- if (typeof chunkOrCallback === "function") {
2930
- first.end(chunkOrCallback);
2931
- return this;
2932
- }
2933
- if (typeof encodingOrCallback === "function") {
2934
- first.end(chunkOrCallback, encodingOrCallback);
2935
- return this;
2936
- }
2937
- first.end(chunkOrCallback, encodingOrCallback, callback);
2938
- return this;
3040
+ forwardReadable = true;
3041
+ registry.add(last, "readable", () => composed.emit("readable"));
3042
+ };
3043
+ const originalOn = composed.on.bind(composed);
3044
+ const originalOnce = composed.once.bind(composed);
3045
+ composed.on = (event, listener) => {
3046
+ if (event === "data") {
3047
+ ensureDataForwarding();
2939
3048
  }
2940
- pipe(destination) {
2941
- return last.pipe(destination);
3049
+ else if (event === "end") {
3050
+ ensureEndForwarding();
2942
3051
  }
2943
- destroy(error) {
2944
- for (const t of transforms) {
2945
- t.destroy(error);
2946
- }
2947
- super.destroy(error);
3052
+ else if (event === "readable") {
3053
+ ensureReadableForwarding();
2948
3054
  }
2949
- read(size) {
2950
- return typeof last.read === "function" ? last.read(size) : null;
3055
+ return originalOn(event, listener);
3056
+ };
3057
+ composed.once = (event, listener) => {
3058
+ if (event === "data") {
3059
+ ensureDataForwarding();
2951
3060
  }
2952
- async *[Symbol.asyncIterator]() {
2953
- const it = last?.[Symbol.asyncIterator]?.();
2954
- if (it) {
2955
- for await (const chunk of it) {
2956
- yield chunk;
2957
- }
2958
- return;
3061
+ else if (event === "end") {
3062
+ ensureEndForwarding();
3063
+ }
3064
+ else if (event === "readable") {
3065
+ ensureReadableForwarding();
3066
+ }
3067
+ return originalOnce(event, listener);
3068
+ };
3069
+ // Delegate core stream methods
3070
+ const firstAny = first;
3071
+ const lastAny = last;
3072
+ composed.write = (chunk, encodingOrCallback, callback) => {
3073
+ if (typeof encodingOrCallback === "function") {
3074
+ return firstAny.write(chunk, encodingOrCallback);
3075
+ }
3076
+ return firstAny.write(chunk, encodingOrCallback, callback);
3077
+ };
3078
+ composed.end = (chunkOrCallback, encodingOrCallback, callback) => {
3079
+ if (typeof chunkOrCallback === "function") {
3080
+ firstAny.end(chunkOrCallback);
3081
+ return composed;
3082
+ }
3083
+ if (typeof encodingOrCallback === "function") {
3084
+ firstAny.end(chunkOrCallback, encodingOrCallback);
3085
+ return composed;
3086
+ }
3087
+ firstAny.end(chunkOrCallback, encodingOrCallback, callback);
3088
+ return composed;
3089
+ };
3090
+ composed.pipe = (destination) => {
3091
+ return lastAny.pipe(destination);
3092
+ };
3093
+ composed.read = (size) => {
3094
+ return typeof lastAny.read === "function" ? lastAny.read(size) : null;
3095
+ };
3096
+ composed[Symbol.asyncIterator] = async function* () {
3097
+ const it = lastAny?.[Symbol.asyncIterator]?.();
3098
+ if (it) {
3099
+ for await (const chunk of it) {
3100
+ yield chunk;
2959
3101
  }
2960
- yield* super[Symbol.asyncIterator]();
3102
+ return;
2961
3103
  }
2962
- }
2963
- const composed = new ComposedTransform({
2964
- objectMode: first?.objectMode ?? true,
2965
- transform: chunk => chunk
3104
+ yield* Transform.prototype[Symbol.asyncIterator].call(composed);
3105
+ };
3106
+ const originalDestroy = composed.destroy.bind(composed);
3107
+ composed.destroy = ((error) => {
3108
+ registry.cleanup();
3109
+ for (const t of transforms) {
3110
+ t.destroy(error);
3111
+ }
3112
+ originalDestroy(error);
2966
3113
  });
2967
- // Forward errors from any transform
2968
- for (const t of transforms) {
2969
- t.on("error", (err) => {
2970
- composed.emit("error", err);
2971
- });
2972
- }
2973
3114
  // Reflect underlying readability/writability like the previous duck-typed wrapper
2974
3115
  Object.defineProperty(composed, "readable", {
2975
3116
  get: () => last.readable
@@ -3215,77 +3356,54 @@ export function duplexPair(options) {
3215
3356
  async function collectStreamChunks(stream) {
3216
3357
  const chunks = [];
3217
3358
  let totalLength = 0;
3218
- let iterable;
3219
- if (isReadableStream(stream)) {
3220
- iterable = Readable.fromWeb(stream);
3221
- }
3222
- else if (isAsyncIterable(stream)) {
3223
- iterable = stream;
3224
- }
3225
- else {
3226
- throw new Error("collectStreamChunks: unsupported stream type");
3227
- }
3359
+ const iterable = toReadableAsyncIterable(stream, "collectStreamChunks");
3228
3360
  for await (const chunk of iterable) {
3229
3361
  chunks.push(chunk);
3230
3362
  totalLength += chunk.length;
3231
3363
  }
3232
- return { chunks, totalLength };
3364
+ return [chunks, totalLength];
3233
3365
  }
3234
- // Helper to concatenate with known length (faster)
3235
- function concatWithLength(chunks, totalLength) {
3236
- const len = chunks.length;
3237
- if (len === 0) {
3238
- return new Uint8Array(0);
3239
- }
3240
- if (len === 1) {
3241
- return chunks[0];
3366
+ function toReadableAsyncIterable(stream, name) {
3367
+ if (isReadableStream(stream)) {
3368
+ return Readable.fromWeb(stream);
3242
3369
  }
3243
- const result = new Uint8Array(totalLength);
3244
- let offset = 0;
3245
- for (let i = 0; i < len; i++) {
3246
- result.set(chunks[i], offset);
3247
- offset += chunks[i].length;
3370
+ if (isAsyncIterable(stream)) {
3371
+ return stream;
3248
3372
  }
3249
- return result;
3373
+ throw new Error(`${name}: unsupported stream type`);
3250
3374
  }
3251
3375
  export const consumers = {
3252
3376
  /**
3253
3377
  * Consume entire stream as ArrayBuffer
3254
3378
  */
3255
3379
  async arrayBuffer(stream) {
3256
- const { chunks, totalLength } = await collectStreamChunks(stream);
3257
- const combined = concatWithLength(chunks, totalLength);
3258
- return combined.buffer.slice(combined.byteOffset, combined.byteOffset + combined.byteLength);
3380
+ const bytes = await streamToUint8Array(stream);
3381
+ return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
3259
3382
  },
3260
3383
  /**
3261
3384
  * Consume entire stream as Blob
3262
3385
  */
3263
3386
  async blob(stream, options) {
3264
- const { chunks } = await collectStreamChunks(stream);
3387
+ const [chunks] = await collectStreamChunks(stream);
3265
3388
  return new Blob(chunks, options);
3266
3389
  },
3267
3390
  /**
3268
3391
  * Consume entire stream as Buffer (Uint8Array in browser)
3269
3392
  */
3270
3393
  async buffer(stream) {
3271
- const { chunks, totalLength } = await collectStreamChunks(stream);
3272
- return concatWithLength(chunks, totalLength);
3394
+ return streamToUint8Array(stream);
3273
3395
  },
3274
3396
  /**
3275
3397
  * Consume entire stream as JSON
3276
3398
  */
3277
3399
  async json(stream) {
3278
- const text = await consumers.text(stream);
3279
- return JSON.parse(text);
3400
+ return JSON.parse(await streamToString(stream));
3280
3401
  },
3281
3402
  /**
3282
3403
  * Consume entire stream as text
3283
3404
  */
3284
3405
  async text(stream, encoding) {
3285
- const { chunks, totalLength } = await collectStreamChunks(stream);
3286
- const combined = concatWithLength(chunks, totalLength);
3287
- const decoder = encoding ? getTextDecoder(encoding) : textDecoder;
3288
- return decoder.decode(combined);
3406
+ return streamToString(stream, encoding);
3289
3407
  }
3290
3408
  };
3291
3409
  // =============================================================================