@cj-tech-master/excelts 4.2.1 → 4.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD_PARTY_NOTICES.md +0 -31
- package/dist/browser/index.browser.d.ts +1 -0
- package/dist/browser/index.browser.js +12 -0
- package/dist/{esm/modules/archive → browser/modules/archive/compression}/compress.base.js +1 -1
- package/dist/{types/modules/archive → browser/modules/archive/compression}/compress.browser.d.ts +2 -8
- package/dist/browser/modules/archive/{compress.browser.js → compression/compress.browser.js} +3 -11
- package/dist/browser/modules/archive/{compress.d.ts → compression/compress.d.ts} +2 -2
- package/dist/{esm/modules/archive → browser/modules/archive/compression}/compress.js +1 -1
- package/dist/browser/modules/archive/{crc32.browser.d.ts → compression/crc32.browser.d.ts} +1 -1
- package/dist/browser/modules/archive/{crc32.d.ts → compression/crc32.d.ts} +1 -1
- package/dist/browser/modules/archive/{crc32.js → compression/crc32.js} +1 -1
- package/dist/browser/modules/archive/{deflate-fallback.js → compression/deflate-fallback.js} +1 -1
- package/dist/browser/modules/archive/{streaming-compress.browser.d.ts → compression/streaming-compress.browser.d.ts} +2 -2
- package/dist/browser/modules/archive/{streaming-compress.browser.js → compression/streaming-compress.browser.js} +3 -3
- package/dist/browser/modules/archive/{streaming-compress.d.ts → compression/streaming-compress.d.ts} +2 -2
- package/dist/browser/modules/archive/{streaming-compress.js → compression/streaming-compress.js} +2 -2
- package/dist/browser/modules/archive/defaults.d.ts +1 -0
- package/dist/browser/modules/archive/defaults.js +6 -3
- package/dist/browser/modules/archive/index.base.d.ts +4 -4
- package/dist/browser/modules/archive/index.base.js +3 -6
- package/dist/browser/modules/archive/index.browser.d.ts +3 -4
- package/dist/browser/modules/archive/index.browser.js +3 -7
- package/dist/browser/modules/archive/index.d.ts +3 -4
- package/dist/browser/modules/archive/index.js +3 -5
- package/dist/browser/modules/archive/internal/byte-queue.d.ts +33 -0
- package/dist/browser/modules/archive/internal/byte-queue.js +407 -0
- package/dist/browser/modules/archive/io/archive-sink.d.ts +8 -0
- package/dist/browser/modules/archive/io/archive-sink.js +45 -0
- package/dist/browser/modules/archive/io/archive-source.d.ts +6 -0
- package/dist/browser/modules/archive/io/archive-source.js +100 -0
- package/dist/browser/modules/archive/{extract.d.ts → unzip/extract.d.ts} +2 -2
- package/dist/browser/modules/archive/unzip/index.d.ts +40 -0
- package/dist/browser/modules/archive/unzip/index.js +164 -0
- package/dist/browser/modules/archive/{parse.base.d.ts → unzip/stream.base.d.ts} +36 -2
- package/dist/browser/modules/archive/unzip/stream.base.js +1022 -0
- package/dist/browser/modules/archive/{parse.browser.d.ts → unzip/stream.browser.d.ts} +1 -1
- package/dist/browser/modules/archive/{parse.browser.js → unzip/stream.browser.js} +371 -110
- package/dist/browser/modules/archive/{parse.d.ts → unzip/stream.d.ts} +2 -2
- package/dist/{esm/modules/archive/parse.js → browser/modules/archive/unzip/stream.js} +6 -5
- package/dist/browser/modules/archive/{zip-parser.d.ts → unzip/zip-parser.d.ts} +1 -1
- package/dist/{esm/modules/archive → browser/modules/archive/unzip}/zip-parser.js +38 -24
- package/dist/browser/modules/archive/utils/async-queue.d.ts +7 -0
- package/dist/browser/modules/archive/utils/async-queue.js +103 -0
- package/dist/browser/modules/archive/utils/bytes.js +16 -16
- package/dist/browser/modules/archive/utils/compressibility.d.ts +10 -0
- package/dist/browser/modules/archive/utils/compressibility.js +57 -0
- package/dist/browser/modules/archive/utils/parse-buffer.js +21 -23
- package/dist/browser/modules/archive/utils/pattern-scanner.d.ts +21 -0
- package/dist/browser/modules/archive/utils/pattern-scanner.js +27 -0
- package/dist/browser/modules/archive/utils/timestamps.js +62 -1
- package/dist/browser/modules/archive/utils/zip-extra-fields.d.ts +1 -1
- package/dist/browser/modules/archive/utils/zip-extra-fields.js +26 -14
- package/dist/browser/modules/archive/zip/index.d.ts +42 -0
- package/dist/browser/modules/archive/zip/index.js +157 -0
- package/dist/browser/modules/archive/{streaming-zip.d.ts → zip/stream.d.ts} +28 -5
- package/dist/browser/modules/archive/{streaming-zip.js → zip/stream.js} +192 -48
- package/dist/browser/modules/archive/zip/zip-bytes.d.ts +73 -0
- package/dist/browser/modules/archive/zip/zip-bytes.js +239 -0
- package/dist/{esm/modules/archive → browser/modules/archive/zip}/zip-entry-metadata.js +3 -3
- package/dist/browser/modules/archive/{zip-records.d.ts → zip-spec/zip-records.d.ts} +20 -0
- package/dist/browser/modules/archive/zip-spec/zip-records.js +126 -0
- package/dist/browser/modules/excel/stream/workbook-reader.browser.js +1 -1
- package/dist/browser/modules/excel/stream/workbook-writer.browser.d.ts +1 -1
- package/dist/browser/modules/excel/stream/workbook-writer.browser.js +1 -1
- package/dist/browser/modules/excel/utils/ooxml-validator.d.ts +48 -0
- package/dist/browser/modules/excel/utils/ooxml-validator.js +469 -0
- package/dist/browser/modules/excel/worksheet.js +5 -2
- package/dist/browser/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.d.ts +1 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.js +13 -1
- package/dist/browser/modules/excel/xlsx/xform/drawing/sp-xform.d.ts +18 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/sp-xform.js +112 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.d.ts +6 -1
- package/dist/browser/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +30 -2
- package/dist/browser/modules/excel/xlsx/xform/drawing/vml-drawing-xform.js +11 -0
- package/dist/browser/modules/excel/xlsx/xform/sheet/page-setup-xform.d.ts +1 -0
- package/dist/browser/modules/excel/xlsx/xform/sheet/page-setup-xform.js +16 -2
- package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +110 -12
- package/dist/browser/modules/excel/xlsx/xlsx.browser.js +3 -6
- package/dist/browser/modules/excel/xlsx/xlsx.js +1 -1
- package/dist/browser/modules/stream/base-transform.d.ts +3 -0
- package/dist/browser/modules/stream/base-transform.js +34 -20
- package/dist/browser/modules/stream/buffered-stream.d.ts +2 -12
- package/dist/browser/modules/stream/chunked-builder.js +4 -4
- package/dist/browser/modules/stream/index.browser.d.ts +13 -19
- package/dist/browser/modules/stream/index.browser.js +10 -22
- package/dist/browser/modules/stream/index.d.ts +18 -41
- package/dist/browser/modules/stream/index.js +15 -44
- package/dist/browser/modules/stream/internal/event-utils.d.ts +17 -0
- package/dist/browser/modules/stream/internal/event-utils.js +40 -0
- package/dist/browser/modules/stream/internal/type-guards.d.ts +9 -0
- package/dist/browser/modules/stream/internal/type-guards.js +24 -0
- package/dist/browser/modules/stream/pull-stream.d.ts +5 -6
- package/dist/browser/modules/stream/pull-stream.js +107 -43
- package/dist/browser/modules/stream/shared.d.ts +1 -1
- package/dist/browser/modules/stream/shared.js +7 -4
- package/dist/browser/modules/stream/streams.browser.d.ts +32 -44
- package/dist/browser/modules/stream/streams.browser.js +921 -836
- package/dist/browser/modules/stream/streams.d.ts +4 -20
- package/dist/browser/modules/stream/streams.js +146 -95
- package/dist/browser/modules/stream/utils.js +5 -38
- package/dist/cjs/modules/archive/{compress.base.js → compression/compress.base.js} +1 -1
- package/dist/cjs/modules/archive/{compress.browser.js → compression/compress.browser.js} +3 -11
- package/dist/cjs/modules/archive/{compress.js → compression/compress.js} +1 -1
- package/dist/cjs/modules/archive/{crc32.js → compression/crc32.js} +1 -1
- package/dist/cjs/modules/archive/{deflate-fallback.js → compression/deflate-fallback.js} +1 -1
- package/dist/cjs/modules/archive/{streaming-compress.browser.js → compression/streaming-compress.browser.js} +3 -3
- package/dist/cjs/modules/archive/{streaming-compress.js → compression/streaming-compress.js} +2 -2
- package/dist/cjs/modules/archive/defaults.js +7 -4
- package/dist/cjs/modules/archive/index.base.js +9 -19
- package/dist/cjs/modules/archive/index.browser.js +4 -10
- package/dist/cjs/modules/archive/index.js +4 -8
- package/dist/cjs/modules/archive/internal/byte-queue.js +411 -0
- package/dist/cjs/modules/archive/io/archive-sink.js +49 -0
- package/dist/cjs/modules/archive/io/archive-source.js +105 -0
- package/dist/cjs/modules/archive/unzip/index.js +170 -0
- package/dist/cjs/modules/archive/unzip/stream.base.js +1044 -0
- package/dist/cjs/modules/archive/{parse.browser.js → unzip/stream.browser.js} +372 -111
- package/dist/cjs/modules/archive/{parse.js → unzip/stream.js} +9 -8
- package/dist/cjs/modules/archive/{zip-parser.js → unzip/zip-parser.js} +47 -33
- package/dist/cjs/modules/archive/utils/async-queue.js +106 -0
- package/dist/cjs/modules/archive/utils/bytes.js +16 -16
- package/dist/cjs/modules/archive/utils/compressibility.js +60 -0
- package/dist/cjs/modules/archive/utils/parse-buffer.js +21 -23
- package/dist/cjs/modules/archive/utils/pattern-scanner.js +31 -0
- package/dist/cjs/modules/archive/utils/timestamps.js +64 -3
- package/dist/cjs/modules/archive/utils/zip-extra-fields.js +26 -14
- package/dist/cjs/modules/archive/zip/index.js +162 -0
- package/dist/cjs/modules/archive/{streaming-zip.js → zip/stream.js} +194 -50
- package/dist/cjs/modules/archive/zip/zip-bytes.js +242 -0
- package/dist/cjs/modules/archive/{zip-entry-metadata.js → zip/zip-entry-metadata.js} +5 -5
- package/dist/cjs/modules/archive/zip-spec/zip-records.js +136 -0
- package/dist/cjs/modules/excel/stream/workbook-reader.browser.js +2 -2
- package/dist/cjs/modules/excel/stream/workbook-writer.browser.js +4 -4
- package/dist/cjs/modules/excel/utils/ooxml-validator.js +475 -0
- package/dist/cjs/modules/excel/worksheet.js +5 -2
- package/dist/cjs/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.js +13 -1
- package/dist/cjs/modules/excel/xlsx/xform/drawing/sp-xform.js +115 -0
- package/dist/cjs/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +30 -2
- package/dist/cjs/modules/excel/xlsx/xform/drawing/vml-drawing-xform.js +11 -0
- package/dist/cjs/modules/excel/xlsx/xform/sheet/page-setup-xform.js +16 -2
- package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +110 -12
- package/dist/cjs/modules/excel/xlsx/xlsx.browser.js +6 -9
- package/dist/cjs/modules/excel/xlsx/xlsx.js +2 -2
- package/dist/cjs/modules/stream/base-transform.js +34 -20
- package/dist/cjs/modules/stream/chunked-builder.js +4 -4
- package/dist/cjs/modules/stream/index.browser.js +10 -17
- package/dist/cjs/modules/stream/index.js +15 -39
- package/dist/cjs/modules/stream/internal/event-utils.js +43 -0
- package/dist/cjs/modules/stream/internal/type-guards.js +30 -0
- package/dist/cjs/modules/stream/pull-stream.js +107 -43
- package/dist/cjs/modules/stream/shared.js +7 -4
- package/dist/cjs/modules/stream/streams.browser.js +927 -847
- package/dist/cjs/modules/stream/streams.js +156 -107
- package/dist/cjs/modules/stream/utils.js +3 -36
- package/dist/esm/index.browser.js +12 -0
- package/dist/{browser/modules/archive → esm/modules/archive/compression}/compress.base.js +1 -1
- package/dist/esm/modules/archive/{compress.browser.js → compression/compress.browser.js} +3 -11
- package/dist/{browser/modules/archive → esm/modules/archive/compression}/compress.js +1 -1
- package/dist/esm/modules/archive/{crc32.js → compression/crc32.js} +1 -1
- package/dist/esm/modules/archive/{deflate-fallback.js → compression/deflate-fallback.js} +1 -1
- package/dist/esm/modules/archive/{streaming-compress.browser.js → compression/streaming-compress.browser.js} +3 -3
- package/dist/esm/modules/archive/{streaming-compress.js → compression/streaming-compress.js} +2 -2
- package/dist/esm/modules/archive/defaults.js +6 -3
- package/dist/esm/modules/archive/index.base.js +3 -6
- package/dist/esm/modules/archive/index.browser.js +3 -7
- package/dist/esm/modules/archive/index.js +3 -5
- package/dist/esm/modules/archive/internal/byte-queue.js +407 -0
- package/dist/esm/modules/archive/io/archive-sink.js +45 -0
- package/dist/esm/modules/archive/io/archive-source.js +100 -0
- package/dist/esm/modules/archive/unzip/index.js +164 -0
- package/dist/esm/modules/archive/unzip/stream.base.js +1022 -0
- package/dist/esm/modules/archive/{parse.browser.js → unzip/stream.browser.js} +371 -110
- package/dist/{browser/modules/archive/parse.js → esm/modules/archive/unzip/stream.js} +6 -5
- package/dist/{browser/modules/archive → esm/modules/archive/unzip}/zip-parser.js +38 -24
- package/dist/esm/modules/archive/utils/async-queue.js +103 -0
- package/dist/esm/modules/archive/utils/bytes.js +16 -16
- package/dist/esm/modules/archive/utils/compressibility.js +57 -0
- package/dist/esm/modules/archive/utils/parse-buffer.js +21 -23
- package/dist/esm/modules/archive/utils/pattern-scanner.js +27 -0
- package/dist/esm/modules/archive/utils/timestamps.js +62 -1
- package/dist/esm/modules/archive/utils/zip-extra-fields.js +26 -14
- package/dist/esm/modules/archive/zip/index.js +157 -0
- package/dist/esm/modules/archive/{streaming-zip.js → zip/stream.js} +192 -48
- package/dist/esm/modules/archive/zip/zip-bytes.js +239 -0
- package/dist/{browser/modules/archive → esm/modules/archive/zip}/zip-entry-metadata.js +3 -3
- package/dist/esm/modules/archive/zip-spec/zip-records.js +126 -0
- package/dist/esm/modules/excel/stream/workbook-reader.browser.js +1 -1
- package/dist/esm/modules/excel/stream/workbook-writer.browser.js +1 -1
- package/dist/esm/modules/excel/utils/ooxml-validator.js +469 -0
- package/dist/esm/modules/excel/worksheet.js +5 -2
- package/dist/esm/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.js +13 -1
- package/dist/esm/modules/excel/xlsx/xform/drawing/sp-xform.js +112 -0
- package/dist/esm/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.js +30 -2
- package/dist/esm/modules/excel/xlsx/xform/drawing/vml-drawing-xform.js +11 -0
- package/dist/esm/modules/excel/xlsx/xform/sheet/page-setup-xform.js +16 -2
- package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +110 -12
- package/dist/esm/modules/excel/xlsx/xlsx.browser.js +3 -6
- package/dist/esm/modules/excel/xlsx/xlsx.js +1 -1
- package/dist/esm/modules/stream/base-transform.js +34 -20
- package/dist/esm/modules/stream/chunked-builder.js +4 -4
- package/dist/esm/modules/stream/index.browser.js +10 -22
- package/dist/esm/modules/stream/index.js +15 -44
- package/dist/esm/modules/stream/internal/event-utils.js +40 -0
- package/dist/esm/modules/stream/internal/type-guards.js +24 -0
- package/dist/esm/modules/stream/pull-stream.js +107 -43
- package/dist/esm/modules/stream/shared.js +7 -4
- package/dist/esm/modules/stream/streams.browser.js +921 -836
- package/dist/esm/modules/stream/streams.js +146 -95
- package/dist/esm/modules/stream/utils.js +5 -38
- package/dist/iife/THIRD_PARTY_NOTICES.md +0 -31
- package/dist/iife/excelts.iife.js +6495 -4440
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +103 -31
- package/dist/types/index.browser.d.ts +1 -0
- package/dist/{browser/modules/archive → types/modules/archive/compression}/compress.browser.d.ts +2 -8
- package/dist/types/modules/archive/{streaming-compress.browser.d.ts → compression/streaming-compress.browser.d.ts} +1 -1
- package/dist/types/modules/archive/defaults.d.ts +1 -0
- package/dist/types/modules/archive/index.base.d.ts +4 -4
- package/dist/types/modules/archive/index.browser.d.ts +3 -4
- package/dist/types/modules/archive/index.d.ts +3 -4
- package/dist/types/modules/archive/internal/byte-queue.d.ts +33 -0
- package/dist/types/modules/archive/io/archive-sink.d.ts +8 -0
- package/dist/types/modules/archive/io/archive-source.d.ts +6 -0
- package/dist/types/modules/archive/unzip/index.d.ts +40 -0
- package/dist/types/modules/archive/{parse.base.d.ts → unzip/stream.base.d.ts} +38 -4
- package/dist/types/modules/archive/{parse.browser.d.ts → unzip/stream.browser.d.ts} +2 -2
- package/dist/types/modules/archive/{parse.d.ts → unzip/stream.d.ts} +3 -3
- package/dist/types/modules/archive/{zip-parser.d.ts → unzip/zip-parser.d.ts} +1 -1
- package/dist/types/modules/archive/utils/async-queue.d.ts +7 -0
- package/dist/types/modules/archive/utils/compressibility.d.ts +10 -0
- package/dist/types/modules/archive/utils/pattern-scanner.d.ts +21 -0
- package/dist/types/modules/archive/utils/zip-extra-fields.d.ts +1 -1
- package/dist/types/modules/archive/zip/index.d.ts +42 -0
- package/dist/types/modules/archive/{streaming-zip.d.ts → zip/stream.d.ts} +29 -6
- package/dist/types/modules/archive/zip/zip-bytes.d.ts +73 -0
- package/dist/types/modules/archive/{zip-entry-metadata.d.ts → zip/zip-entry-metadata.d.ts} +1 -1
- package/dist/types/modules/archive/{zip-records.d.ts → zip-spec/zip-records.d.ts} +20 -0
- package/dist/types/modules/excel/stream/workbook-writer.browser.d.ts +1 -1
- package/dist/types/modules/excel/utils/ooxml-validator.d.ts +48 -0
- package/dist/types/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.d.ts +1 -0
- package/dist/types/modules/excel/xlsx/xform/drawing/sp-xform.d.ts +18 -0
- package/dist/types/modules/excel/xlsx/xform/drawing/two-cell-anchor-xform.d.ts +6 -1
- package/dist/types/modules/excel/xlsx/xform/sheet/page-setup-xform.d.ts +1 -0
- package/dist/types/modules/stream/base-transform.d.ts +3 -0
- package/dist/types/modules/stream/buffered-stream.d.ts +2 -12
- package/dist/types/modules/stream/index.browser.d.ts +13 -19
- package/dist/types/modules/stream/index.d.ts +18 -41
- package/dist/types/modules/stream/internal/event-utils.d.ts +17 -0
- package/dist/types/modules/stream/internal/type-guards.d.ts +9 -0
- package/dist/types/modules/stream/pull-stream.d.ts +5 -6
- package/dist/types/modules/stream/shared.d.ts +1 -1
- package/dist/types/modules/stream/streams.browser.d.ts +32 -44
- package/dist/types/modules/stream/streams.d.ts +4 -20
- package/package.json +14 -10
- package/dist/browser/modules/archive/byte-queue.d.ts +0 -18
- package/dist/browser/modules/archive/byte-queue.js +0 -125
- package/dist/browser/modules/archive/parse.base.js +0 -644
- package/dist/browser/modules/archive/utils/zip-extra.d.ts +0 -18
- package/dist/browser/modules/archive/utils/zip-extra.js +0 -68
- package/dist/browser/modules/archive/zip-builder.d.ts +0 -117
- package/dist/browser/modules/archive/zip-builder.js +0 -292
- package/dist/browser/modules/archive/zip-constants.d.ts +0 -18
- package/dist/browser/modules/archive/zip-constants.js +0 -23
- package/dist/browser/modules/archive/zip-records.js +0 -84
- package/dist/cjs/modules/archive/byte-queue.js +0 -129
- package/dist/cjs/modules/archive/parse.base.js +0 -666
- package/dist/cjs/modules/archive/utils/zip-extra.js +0 -74
- package/dist/cjs/modules/archive/zip-builder.js +0 -297
- package/dist/cjs/modules/archive/zip-constants.js +0 -26
- package/dist/cjs/modules/archive/zip-records.js +0 -90
- package/dist/esm/modules/archive/byte-queue.js +0 -125
- package/dist/esm/modules/archive/parse.base.js +0 -644
- package/dist/esm/modules/archive/utils/zip-extra.js +0 -68
- package/dist/esm/modules/archive/zip-builder.js +0 -292
- package/dist/esm/modules/archive/zip-constants.js +0 -23
- package/dist/esm/modules/archive/zip-records.js +0 -84
- package/dist/types/modules/archive/byte-queue.d.ts +0 -18
- package/dist/types/modules/archive/utils/zip-extra.d.ts +0 -18
- package/dist/types/modules/archive/zip-builder.d.ts +0 -117
- package/dist/types/modules/archive/zip-constants.d.ts +0 -18
- /package/dist/browser/modules/archive/{compress.base.d.ts → compression/compress.base.d.ts} +0 -0
- /package/dist/browser/modules/archive/{crc32.base.d.ts → compression/crc32.base.d.ts} +0 -0
- /package/dist/browser/modules/archive/{crc32.base.js → compression/crc32.base.js} +0 -0
- /package/dist/browser/modules/archive/{crc32.browser.js → compression/crc32.browser.js} +0 -0
- /package/dist/browser/modules/archive/{deflate-fallback.d.ts → compression/deflate-fallback.d.ts} +0 -0
- /package/dist/browser/modules/archive/{streaming-compress.base.d.ts → compression/streaming-compress.base.d.ts} +0 -0
- /package/dist/browser/modules/archive/{streaming-compress.base.js → compression/streaming-compress.base.js} +0 -0
- /package/dist/browser/modules/archive/{extract.js → unzip/extract.js} +0 -0
- /package/dist/browser/modules/archive/{zip-entry-metadata.d.ts → zip/zip-entry-metadata.d.ts} +0 -0
- /package/dist/browser/modules/archive/{zip-entry-info.d.ts → zip-spec/zip-entry-info.d.ts} +0 -0
- /package/dist/browser/modules/archive/{zip-entry-info.js → zip-spec/zip-entry-info.js} +0 -0
- /package/dist/cjs/modules/archive/{crc32.base.js → compression/crc32.base.js} +0 -0
- /package/dist/cjs/modules/archive/{crc32.browser.js → compression/crc32.browser.js} +0 -0
- /package/dist/cjs/modules/archive/{streaming-compress.base.js → compression/streaming-compress.base.js} +0 -0
- /package/dist/cjs/modules/archive/{extract.js → unzip/extract.js} +0 -0
- /package/dist/cjs/modules/archive/{zip-entry-info.js → zip-spec/zip-entry-info.js} +0 -0
- /package/dist/esm/modules/archive/{crc32.base.js → compression/crc32.base.js} +0 -0
- /package/dist/esm/modules/archive/{crc32.browser.js → compression/crc32.browser.js} +0 -0
- /package/dist/esm/modules/archive/{streaming-compress.base.js → compression/streaming-compress.base.js} +0 -0
- /package/dist/esm/modules/archive/{extract.js → unzip/extract.js} +0 -0
- /package/dist/esm/modules/archive/{zip-entry-info.js → zip-spec/zip-entry-info.js} +0 -0
- /package/dist/types/modules/archive/{compress.base.d.ts → compression/compress.base.d.ts} +0 -0
- /package/dist/types/modules/archive/{compress.d.ts → compression/compress.d.ts} +0 -0
- /package/dist/types/modules/archive/{crc32.base.d.ts → compression/crc32.base.d.ts} +0 -0
- /package/dist/types/modules/archive/{crc32.browser.d.ts → compression/crc32.browser.d.ts} +0 -0
- /package/dist/types/modules/archive/{crc32.d.ts → compression/crc32.d.ts} +0 -0
- /package/dist/types/modules/archive/{deflate-fallback.d.ts → compression/deflate-fallback.d.ts} +0 -0
- /package/dist/types/modules/archive/{streaming-compress.base.d.ts → compression/streaming-compress.base.d.ts} +0 -0
- /package/dist/types/modules/archive/{streaming-compress.d.ts → compression/streaming-compress.d.ts} +0 -0
- /package/dist/types/modules/archive/{extract.d.ts → unzip/extract.d.ts} +0 -0
- /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
|
|
15
|
-
import { BufferedStream
|
|
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
|
-
(
|
|
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
|
-
|
|
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:
|
|
182
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
389
|
+
this._emitEndOnce();
|
|
340
390
|
}
|
|
341
391
|
else if (this._read) {
|
|
342
392
|
// Call user-provided read function asynchronously
|
|
@@ -399,26 +449,36 @@ export class Readable extends EventEmitter {
|
|
|
399
449
|
}
|
|
400
450
|
this._pipeTo.push(dest);
|
|
401
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
|
+
};
|
|
402
465
|
const dataListener = (chunk) => {
|
|
403
466
|
// Call destination's write() method (not internal _writable.write())
|
|
404
467
|
// This ensures Transform.write() logic runs properly
|
|
405
468
|
const canWrite = dest.write(chunk);
|
|
406
469
|
if (!canWrite) {
|
|
407
470
|
this.pause();
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
const resumeOnce = () => {
|
|
413
|
-
if (typeof eventTarget.off === "function") {
|
|
414
|
-
eventTarget.off("drain", resumeOnce);
|
|
415
|
-
}
|
|
416
|
-
else if (typeof eventTarget.removeListener === "function") {
|
|
417
|
-
eventTarget.removeListener("drain", resumeOnce);
|
|
418
|
-
}
|
|
471
|
+
// Install a removable, once-style drain listener.
|
|
472
|
+
if (!drainListener) {
|
|
473
|
+
drainListener = () => {
|
|
474
|
+
removeDrainListener();
|
|
419
475
|
this.resume();
|
|
420
476
|
};
|
|
421
|
-
eventTarget.on("drain",
|
|
477
|
+
eventTarget.on("drain", drainListener);
|
|
478
|
+
const entry = this._pipeListeners.get(dest);
|
|
479
|
+
if (entry) {
|
|
480
|
+
entry.drain = drainListener;
|
|
481
|
+
}
|
|
422
482
|
}
|
|
423
483
|
}
|
|
424
484
|
};
|
|
@@ -438,7 +498,8 @@ export class Readable extends EventEmitter {
|
|
|
438
498
|
this._pipeListeners.set(dest, {
|
|
439
499
|
data: dataListener,
|
|
440
500
|
end: endListener,
|
|
441
|
-
error: errorListener
|
|
501
|
+
error: errorListener,
|
|
502
|
+
eventTarget
|
|
442
503
|
});
|
|
443
504
|
this.on("data", dataListener);
|
|
444
505
|
this.once("end", endListener);
|
|
@@ -461,6 +522,14 @@ export class Readable extends EventEmitter {
|
|
|
461
522
|
this.off("data", listeners.data);
|
|
462
523
|
this.off("end", listeners.end);
|
|
463
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
|
+
}
|
|
464
533
|
this._pipeListeners.delete(destination);
|
|
465
534
|
}
|
|
466
535
|
}
|
|
@@ -472,6 +541,14 @@ export class Readable extends EventEmitter {
|
|
|
472
541
|
this.off("data", listeners.data);
|
|
473
542
|
this.off("end", listeners.end);
|
|
474
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
|
+
}
|
|
475
552
|
this._pipeListeners.delete(target);
|
|
476
553
|
}
|
|
477
554
|
}
|
|
@@ -490,12 +567,26 @@ export class Readable extends EventEmitter {
|
|
|
490
567
|
}
|
|
491
568
|
this._destroyed = true;
|
|
492
569
|
this._ended = true;
|
|
570
|
+
// Ensure we detach from destinations to avoid leaking listeners.
|
|
571
|
+
this.unpipe();
|
|
493
572
|
if (error) {
|
|
494
573
|
this._errored = error;
|
|
495
574
|
this.emit("error", error);
|
|
496
575
|
}
|
|
497
576
|
if (this._reader) {
|
|
498
|
-
this._reader
|
|
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
|
+
});
|
|
499
590
|
}
|
|
500
591
|
this._closed = true;
|
|
501
592
|
this.emit("close");
|
|
@@ -574,18 +665,38 @@ export class Readable extends EventEmitter {
|
|
|
574
665
|
const { done, value } = await this._reader.read();
|
|
575
666
|
// Check _pushMode again after async read - if push() was called, stop reading
|
|
576
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
|
+
}
|
|
577
678
|
break;
|
|
578
679
|
}
|
|
579
680
|
if (done) {
|
|
580
681
|
this._ended = true;
|
|
581
|
-
this.
|
|
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
|
+
}
|
|
582
693
|
break;
|
|
583
694
|
}
|
|
584
695
|
if (value !== undefined) {
|
|
585
696
|
// In flowing mode, emit data directly without buffering
|
|
586
697
|
// Only buffer if not flowing (paused mode)
|
|
587
698
|
if (this._flowing) {
|
|
588
|
-
this.emit("data", value);
|
|
699
|
+
this.emit("data", this._applyEncoding(value));
|
|
589
700
|
}
|
|
590
701
|
else {
|
|
591
702
|
this._buffer.push(value);
|
|
@@ -598,6 +709,16 @@ export class Readable extends EventEmitter {
|
|
|
598
709
|
}
|
|
599
710
|
catch (err) {
|
|
600
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
|
+
}
|
|
601
722
|
}
|
|
602
723
|
finally {
|
|
603
724
|
this._reading = false;
|
|
@@ -605,7 +726,8 @@ export class Readable extends EventEmitter {
|
|
|
605
726
|
}
|
|
606
727
|
/**
|
|
607
728
|
* Async iterator support
|
|
608
|
-
* Uses
|
|
729
|
+
* Uses a unified event-queue iterator with simple backpressure.
|
|
730
|
+
* This matches Node's behavior more closely (iterator drives flowing mode).
|
|
609
731
|
*/
|
|
610
732
|
async *[Symbol.asyncIterator]() {
|
|
611
733
|
// First yield any buffered data
|
|
@@ -614,117 +736,124 @@ export class Readable extends EventEmitter {
|
|
|
614
736
|
if (!this.objectMode) {
|
|
615
737
|
this._bufferSize -= this._getChunkSize(chunk);
|
|
616
738
|
}
|
|
617
|
-
yield chunk;
|
|
739
|
+
yield this._applyEncoding(chunk);
|
|
618
740
|
}
|
|
619
|
-
// If already ended, we're done
|
|
620
741
|
if (this._ended) {
|
|
621
742
|
return;
|
|
622
743
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
let resolveNext = null;
|
|
629
|
-
let rejectNext = null;
|
|
630
|
-
let done = false;
|
|
631
|
-
let streamError = null;
|
|
632
|
-
let dataQueueIndex = 0;
|
|
633
|
-
const dataHandler = (chunk) => {
|
|
634
|
-
if (resolveNext) {
|
|
635
|
-
resolveNext(chunk);
|
|
636
|
-
resolveNext = null;
|
|
637
|
-
rejectNext = null;
|
|
638
|
-
}
|
|
639
|
-
else {
|
|
640
|
-
dataQueue.push(chunk);
|
|
641
|
-
}
|
|
642
|
-
};
|
|
643
|
-
const endHandler = () => {
|
|
644
|
-
done = true;
|
|
645
|
-
if (resolveNext) {
|
|
646
|
-
resolveNext(null);
|
|
647
|
-
resolveNext = null;
|
|
648
|
-
rejectNext = null;
|
|
649
|
-
}
|
|
650
|
-
};
|
|
651
|
-
const errorHandler = (err) => {
|
|
652
|
-
done = true;
|
|
653
|
-
streamError = err;
|
|
654
|
-
if (rejectNext) {
|
|
655
|
-
rejectNext(err);
|
|
656
|
-
resolveNext = null;
|
|
657
|
-
rejectNext = null;
|
|
658
|
-
}
|
|
659
|
-
};
|
|
660
|
-
const closeHandler = () => {
|
|
661
|
-
// If stream closed without end event (e.g., after destroy()),
|
|
662
|
-
// treat it as done
|
|
663
|
-
done = true;
|
|
664
|
-
if (resolveNext) {
|
|
665
|
-
resolveNext(null);
|
|
666
|
-
resolveNext = null;
|
|
667
|
-
rejectNext = null;
|
|
668
|
-
}
|
|
669
|
-
};
|
|
670
|
-
this.on("data", dataHandler);
|
|
671
|
-
this.on("end", endHandler);
|
|
672
|
-
this.on("error", errorHandler);
|
|
673
|
-
this.on("close", closeHandler);
|
|
674
|
-
try {
|
|
675
|
-
// Enter flowing mode
|
|
676
|
-
this.resume();
|
|
677
|
-
while (!done || dataQueueIndex < dataQueue.length) {
|
|
678
|
-
// Check for error before processing
|
|
679
|
-
if (streamError) {
|
|
680
|
-
throw streamError;
|
|
681
|
-
}
|
|
682
|
-
if (dataQueueIndex < dataQueue.length) {
|
|
683
|
-
const chunk = dataQueue[dataQueueIndex++];
|
|
684
|
-
if (dataQueueIndex >= 1024 && dataQueueIndex * 2 >= dataQueue.length) {
|
|
685
|
-
dataQueue.splice(0, dataQueueIndex);
|
|
686
|
-
dataQueueIndex = 0;
|
|
687
|
-
}
|
|
688
|
-
yield chunk;
|
|
689
|
-
}
|
|
690
|
-
else if (!done) {
|
|
691
|
-
const chunk = await new Promise((resolve, reject) => {
|
|
692
|
-
resolveNext = resolve;
|
|
693
|
-
rejectNext = reject;
|
|
694
|
-
});
|
|
695
|
-
if (chunk !== null) {
|
|
696
|
-
yield chunk;
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
// Check for error after loop
|
|
701
|
-
if (streamError) {
|
|
702
|
-
throw streamError;
|
|
703
|
-
}
|
|
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;
|
|
704
749
|
}
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
this.off("end", endHandler);
|
|
708
|
-
this.off("error", errorHandler);
|
|
709
|
-
this.off("close", closeHandler);
|
|
750
|
+
if (chunk instanceof Uint8Array) {
|
|
751
|
+
return chunk.byteLength;
|
|
710
752
|
}
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
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);
|
|
717
811
|
try {
|
|
812
|
+
// Iterator consumption should drive the stream.
|
|
813
|
+
this.resume();
|
|
718
814
|
while (true) {
|
|
719
|
-
|
|
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
|
+
}
|
|
720
832
|
if (done) {
|
|
721
833
|
break;
|
|
722
834
|
}
|
|
723
|
-
|
|
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;
|
|
724
850
|
}
|
|
725
851
|
}
|
|
726
852
|
finally {
|
|
727
|
-
this.
|
|
853
|
+
this.off("data", dataHandler);
|
|
854
|
+
this.off("end", endHandler);
|
|
855
|
+
this.off("error", errorHandler);
|
|
856
|
+
this.off("close", closeHandler);
|
|
728
857
|
}
|
|
729
858
|
}
|
|
730
859
|
/**
|
|
@@ -943,6 +1072,32 @@ export class Readable extends EventEmitter {
|
|
|
943
1072
|
return stream;
|
|
944
1073
|
}
|
|
945
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
|
+
}
|
|
946
1101
|
// =============================================================================
|
|
947
1102
|
// Writable Stream Wrapper
|
|
948
1103
|
// =============================================================================
|
|
@@ -960,10 +1115,12 @@ export class Writable extends EventEmitter {
|
|
|
960
1115
|
this._closed = false;
|
|
961
1116
|
this._pendingWrites = 0;
|
|
962
1117
|
this._writableLength = 0;
|
|
1118
|
+
this._needDrain = false;
|
|
963
1119
|
this._corked = 0;
|
|
964
1120
|
this._corkedChunks = [];
|
|
965
1121
|
this._defaultEncoding = "utf8";
|
|
966
1122
|
this._aborted = false;
|
|
1123
|
+
this._ownsStream = false;
|
|
967
1124
|
this.objectMode = options?.objectMode ?? false;
|
|
968
1125
|
this.writableHighWaterMark = options?.highWaterMark ?? 16384;
|
|
969
1126
|
this.autoDestroy = options?.autoDestroy ?? true;
|
|
@@ -979,8 +1136,10 @@ export class Writable extends EventEmitter {
|
|
|
979
1136
|
}
|
|
980
1137
|
if (options?.stream) {
|
|
981
1138
|
this._stream = options.stream;
|
|
1139
|
+
this._ownsStream = false;
|
|
982
1140
|
}
|
|
983
1141
|
else {
|
|
1142
|
+
this._ownsStream = true;
|
|
984
1143
|
// Create bound references to instance properties/methods for use in WritableStream callbacks
|
|
985
1144
|
const getWriteFunc = () => this._writeFunc;
|
|
986
1145
|
const getFinalFunc = () => this._finalFunc;
|
|
@@ -1087,25 +1246,37 @@ export class Writable extends EventEmitter {
|
|
|
1087
1246
|
this._writableLength += chunkSize;
|
|
1088
1247
|
return this._writableLength < this.writableHighWaterMark;
|
|
1089
1248
|
}
|
|
1090
|
-
|
|
1249
|
+
const ok = this._doWrite(chunk, cb);
|
|
1250
|
+
if (!ok) {
|
|
1251
|
+
this._needDrain = true;
|
|
1252
|
+
}
|
|
1253
|
+
return ok;
|
|
1091
1254
|
}
|
|
1092
1255
|
_doWrite(chunk, callback) {
|
|
1093
1256
|
// Track pending writes for writableLength
|
|
1094
1257
|
const chunkSize = this._getChunkSize(chunk);
|
|
1095
1258
|
this._pendingWrites++;
|
|
1096
1259
|
this._writableLength += chunkSize;
|
|
1097
|
-
this._getWriter()
|
|
1260
|
+
const writer = this._getWriter();
|
|
1261
|
+
writer
|
|
1098
1262
|
.write(chunk)
|
|
1099
1263
|
.then(() => {
|
|
1100
1264
|
this._pendingWrites--;
|
|
1101
1265
|
this._writableLength -= chunkSize;
|
|
1102
|
-
this.
|
|
1266
|
+
if (this._needDrain && this._writableLength < this.writableHighWaterMark) {
|
|
1267
|
+
this._needDrain = false;
|
|
1268
|
+
this.emit("drain");
|
|
1269
|
+
}
|
|
1103
1270
|
callback?.(null);
|
|
1104
1271
|
})
|
|
1105
1272
|
.catch(err => {
|
|
1106
1273
|
this._pendingWrites--;
|
|
1107
1274
|
this._writableLength -= chunkSize;
|
|
1108
|
-
|
|
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
|
+
}
|
|
1109
1280
|
callback?.(err);
|
|
1110
1281
|
});
|
|
1111
1282
|
// Return false if we've exceeded high water mark (for backpressure)
|
|
@@ -1136,12 +1307,29 @@ export class Writable extends EventEmitter {
|
|
|
1136
1307
|
: callback;
|
|
1137
1308
|
const finish = async () => {
|
|
1138
1309
|
try {
|
|
1310
|
+
const writer = this._getWriter();
|
|
1139
1311
|
if (chunk !== undefined) {
|
|
1140
|
-
await
|
|
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
|
+
}
|
|
1141
1332
|
}
|
|
1142
|
-
await this._getWriter().close();
|
|
1143
|
-
this._finished = true;
|
|
1144
|
-
this.emit("finish");
|
|
1145
1333
|
if (cb) {
|
|
1146
1334
|
cb();
|
|
1147
1335
|
}
|
|
@@ -1162,12 +1350,24 @@ export class Writable extends EventEmitter {
|
|
|
1162
1350
|
}
|
|
1163
1351
|
this._destroyed = true;
|
|
1164
1352
|
this._ended = true;
|
|
1165
|
-
if (error) {
|
|
1353
|
+
if (error && !this._errored) {
|
|
1166
1354
|
this._errored = error;
|
|
1167
1355
|
this.emit("error", error);
|
|
1168
1356
|
}
|
|
1169
1357
|
if (this._writer) {
|
|
1170
|
-
this._writer
|
|
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
|
+
});
|
|
1171
1371
|
}
|
|
1172
1372
|
this._closed = true;
|
|
1173
1373
|
this.emit("close");
|
|
@@ -1318,342 +1518,259 @@ export function normalizeWritable(stream) {
|
|
|
1318
1518
|
*/
|
|
1319
1519
|
export class Transform extends EventEmitter {
|
|
1320
1520
|
/**
|
|
1321
|
-
* Push data to the readable side (Node.js compatibility)
|
|
1322
|
-
*
|
|
1521
|
+
* Push data to the readable side (Node.js compatibility).
|
|
1522
|
+
* Intended to be called from within transform/flush.
|
|
1323
1523
|
*/
|
|
1324
1524
|
push(chunk) {
|
|
1325
|
-
|
|
1326
|
-
return false;
|
|
1327
|
-
}
|
|
1328
|
-
if (this._transformController) {
|
|
1329
|
-
// If we're in a transform callback, enqueue directly
|
|
1330
|
-
this._transformController.enqueue(chunk);
|
|
1331
|
-
}
|
|
1332
|
-
else {
|
|
1333
|
-
// Otherwise buffer for later
|
|
1334
|
-
this._pushBuffer.push(chunk);
|
|
1335
|
-
}
|
|
1336
|
-
return true;
|
|
1525
|
+
return this._readable.push(chunk);
|
|
1337
1526
|
}
|
|
1338
1527
|
constructor(options) {
|
|
1339
1528
|
super();
|
|
1340
|
-
this._ended = false;
|
|
1341
1529
|
this._destroyed = false;
|
|
1530
|
+
this._ended = false;
|
|
1342
1531
|
this._errored = false;
|
|
1343
|
-
// Buffer for Node.js style push() calls during transform
|
|
1344
|
-
this._pushBuffer = [];
|
|
1345
|
-
// Controller for enqueueing pushed data (set during transform execution)
|
|
1346
|
-
this._transformController = null;
|
|
1347
|
-
// Buffer for writes that occur after end() but before writable is closed
|
|
1348
|
-
this._pendingEndWrites = [];
|
|
1349
|
-
// Whether end() has been called but writable not yet closed
|
|
1350
|
-
this._endPending = false;
|
|
1351
|
-
// Track if we've already set up data forwarding
|
|
1352
1532
|
this._dataForwardingSetup = false;
|
|
1353
|
-
|
|
1354
|
-
this.
|
|
1355
|
-
|
|
1356
|
-
this._readableConsuming = false;
|
|
1357
|
-
/** @internal - buffer for auto-consumed data */
|
|
1358
|
-
this._autoConsumedBuffer = [];
|
|
1359
|
-
this._autoConsumedBufferIndex = 0;
|
|
1360
|
-
/** @internal - whether auto-consume has ended */
|
|
1361
|
-
this._autoConsumeEnded = false;
|
|
1362
|
-
/** @internal - promise that resolves when auto-consume finishes */
|
|
1363
|
-
this._autoConsumePromise = null;
|
|
1364
|
-
/** @internal - list of piped destinations for forwarding auto-consumed data */
|
|
1365
|
-
this._pipeDestinations = [];
|
|
1533
|
+
this._endTimer = null;
|
|
1534
|
+
this._webStream = null;
|
|
1535
|
+
this._sideForwardingCleanup = null;
|
|
1366
1536
|
this.objectMode = options?.objectMode ?? false;
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
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));
|
|
1382
1556
|
}
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
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;
|
|
1392
1588
|
}
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
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;
|
|
1400
1639
|
}
|
|
1401
|
-
const
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
// Check for subclass _transform override first
|
|
1414
|
-
if (hasSubclassTransform()) {
|
|
1415
|
-
// Call subclass _transform method (Node.js style)
|
|
1416
|
-
// _transform signature is (chunk, encoding, callback)
|
|
1417
|
-
await new Promise((resolve, reject) => {
|
|
1418
|
-
this._transform(chunk, "utf8", (err, data) => {
|
|
1419
|
-
if (err) {
|
|
1420
|
-
reject(err);
|
|
1421
|
-
}
|
|
1422
|
-
else {
|
|
1423
|
-
if (data !== undefined) {
|
|
1424
|
-
controller.enqueue(data);
|
|
1425
|
-
}
|
|
1426
|
-
resolve();
|
|
1427
|
-
}
|
|
1428
|
-
});
|
|
1429
|
-
});
|
|
1430
|
-
}
|
|
1431
|
-
else if (userTransform) {
|
|
1432
|
-
const transformParamCount = userTransform.length;
|
|
1433
|
-
if (transformParamCount >= 3) {
|
|
1434
|
-
// Node.js style: transform(chunk, encoding, callback)
|
|
1435
|
-
await new Promise((resolve, reject) => {
|
|
1436
|
-
userTransform.call(getInstance(), chunk, "utf8", (err, data) => {
|
|
1437
|
-
if (err) {
|
|
1438
|
-
reject(err);
|
|
1439
|
-
}
|
|
1440
|
-
else {
|
|
1441
|
-
if (data !== undefined) {
|
|
1442
|
-
controller.enqueue(data);
|
|
1443
|
-
}
|
|
1444
|
-
resolve();
|
|
1445
|
-
}
|
|
1446
|
-
});
|
|
1447
|
-
});
|
|
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;
|
|
1448
1652
|
}
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
userTransform.call(getInstance(), chunk, (err, data) => {
|
|
1452
|
-
if (err) {
|
|
1453
|
-
reject(err);
|
|
1454
|
-
}
|
|
1455
|
-
else {
|
|
1456
|
-
if (data !== undefined) {
|
|
1457
|
-
controller.enqueue(data);
|
|
1458
|
-
}
|
|
1459
|
-
resolve();
|
|
1460
|
-
}
|
|
1461
|
-
});
|
|
1462
|
-
});
|
|
1653
|
+
if (data !== undefined) {
|
|
1654
|
+
this.push(data);
|
|
1463
1655
|
}
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
controller.enqueue(result);
|
|
1476
|
-
}
|
|
1477
|
-
}
|
|
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;
|
|
1478
1667
|
}
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
}
|
|
1484
|
-
}
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
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);
|
|
1491
1681
|
}
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
if (flushParamCount >= 1) {
|
|
1515
|
-
// Node.js style: flush(callback)
|
|
1516
|
-
await new Promise((resolve, reject) => {
|
|
1517
|
-
userFlush.call(getInstance(), (err, data) => {
|
|
1518
|
-
if (err) {
|
|
1519
|
-
reject(err);
|
|
1520
|
-
}
|
|
1521
|
-
else {
|
|
1522
|
-
if (data !== undefined) {
|
|
1523
|
-
controller.enqueue(data);
|
|
1524
|
-
}
|
|
1525
|
-
resolve();
|
|
1526
|
-
}
|
|
1527
|
-
});
|
|
1528
|
-
});
|
|
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;
|
|
1529
1704
|
}
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
const result = userFlush.call(getInstance());
|
|
1533
|
-
if (result && typeof result.then === "function") {
|
|
1534
|
-
const awaitedResult = await result;
|
|
1535
|
-
if (awaitedResult !== undefined && awaitedResult !== null) {
|
|
1536
|
-
controller.enqueue(awaitedResult);
|
|
1537
|
-
}
|
|
1538
|
-
}
|
|
1539
|
-
else {
|
|
1540
|
-
if (result !== undefined && result !== null) {
|
|
1541
|
-
controller.enqueue(result);
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1705
|
+
if (data !== undefined) {
|
|
1706
|
+
this.push(data);
|
|
1544
1707
|
}
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
}
|
|
1548
|
-
|
|
1549
|
-
controller.error(err);
|
|
1550
|
-
emitEvent("error", err);
|
|
1551
|
-
}
|
|
1552
|
-
finally {
|
|
1553
|
-
setController(null);
|
|
1554
|
-
}
|
|
1708
|
+
resolve();
|
|
1709
|
+
});
|
|
1710
|
+
});
|
|
1711
|
+
return;
|
|
1555
1712
|
}
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
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;
|
|
1732
|
+
}
|
|
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;
|
|
1572
1740
|
}
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
this._writable.on("drain", () => this.emit("drain"));
|
|
1576
|
-
// Only forward errors if not already errored (to prevent duplicate events)
|
|
1577
|
-
this._writable.on("error", err => {
|
|
1578
|
-
if (!this._errored) {
|
|
1579
|
-
this._errored = true;
|
|
1580
|
-
this.emit("error", err);
|
|
1741
|
+
if (result !== undefined && result !== null) {
|
|
1742
|
+
this.push(result);
|
|
1581
1743
|
}
|
|
1582
|
-
}
|
|
1744
|
+
}
|
|
1745
|
+
catch (err) {
|
|
1746
|
+
this._emitErrorOnce(err);
|
|
1747
|
+
throw err;
|
|
1748
|
+
}
|
|
1583
1749
|
}
|
|
1584
1750
|
/**
|
|
1585
|
-
* Override on to
|
|
1751
|
+
* Override on() to lazily forward readable 'data' events.
|
|
1752
|
+
* Avoids starting flowing mode unless requested.
|
|
1586
1753
|
*/
|
|
1587
1754
|
on(event, listener) {
|
|
1588
|
-
// Set up data forwarding when first external data listener is added
|
|
1589
1755
|
if (event === "data" && !this._dataForwardingSetup) {
|
|
1590
1756
|
this._dataForwardingSetup = true;
|
|
1591
|
-
this._readable.on("data",
|
|
1592
|
-
}
|
|
1593
|
-
super.on(event, listener);
|
|
1594
|
-
// When data listener is added, mark as having consumer
|
|
1595
|
-
// and start the readable in flowing mode
|
|
1596
|
-
if (event === "data") {
|
|
1597
|
-
this._hasDataConsumer = true;
|
|
1598
|
-
this._readable.resume();
|
|
1757
|
+
this._readable.on("data", chunk => this.emit("data", chunk));
|
|
1599
1758
|
}
|
|
1600
|
-
return
|
|
1759
|
+
return super.on(event, listener);
|
|
1601
1760
|
}
|
|
1602
1761
|
write(chunk, encodingOrCallback, callback) {
|
|
1603
1762
|
const cb = typeof encodingOrCallback === "function" ? encodingOrCallback : callback;
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
cb?.(err);
|
|
1608
|
-
return false;
|
|
1609
|
-
}
|
|
1610
|
-
// Ensure readable is being consumed to allow transform to execute
|
|
1611
|
-
// This matches Node.js behavior where transform executes immediately on write
|
|
1612
|
-
// Only auto-consume if no explicit consumer (data listener or pipe)
|
|
1613
|
-
if (!this._readableConsuming && !this._hasDataConsumer) {
|
|
1614
|
-
this._readableConsuming = true;
|
|
1615
|
-
this._startAutoConsume();
|
|
1616
|
-
}
|
|
1617
|
-
// If end() was called but writable not yet closed, buffer the write
|
|
1618
|
-
// This allows writes during data event handlers to be processed
|
|
1619
|
-
if (this._endPending) {
|
|
1620
|
-
this._pendingEndWrites.push({ chunk, callback: cb });
|
|
1621
|
-
return true;
|
|
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();
|
|
1622
1766
|
}
|
|
1623
1767
|
return this._writable.write(chunk, cb);
|
|
1624
1768
|
}
|
|
1625
|
-
/** @internal - auto-consume readable to allow transform to execute */
|
|
1626
|
-
_startAutoConsume() {
|
|
1627
|
-
this._autoConsumePromise = (async () => {
|
|
1628
|
-
try {
|
|
1629
|
-
for await (const chunk of this._readable) {
|
|
1630
|
-
// Buffer the data for later retrieval
|
|
1631
|
-
this._autoConsumedBuffer.push(chunk);
|
|
1632
|
-
// Forward to any piped destinations
|
|
1633
|
-
for (const dest of this._pipeDestinations) {
|
|
1634
|
-
dest.write(chunk);
|
|
1635
|
-
}
|
|
1636
|
-
// Also emit data event for listeners
|
|
1637
|
-
this.emit("data", chunk);
|
|
1638
|
-
}
|
|
1639
|
-
this._autoConsumeEnded = true;
|
|
1640
|
-
// End all piped destinations
|
|
1641
|
-
for (const dest of this._pipeDestinations) {
|
|
1642
|
-
dest.end();
|
|
1643
|
-
}
|
|
1644
|
-
this.emit("end");
|
|
1645
|
-
}
|
|
1646
|
-
catch (err) {
|
|
1647
|
-
this.emit("error", err);
|
|
1648
|
-
}
|
|
1649
|
-
})();
|
|
1650
|
-
}
|
|
1651
1769
|
end(chunkOrCallback, encodingOrCallback, callback) {
|
|
1652
1770
|
if (this._ended) {
|
|
1653
1771
|
return this;
|
|
1654
1772
|
}
|
|
1655
1773
|
this._ended = true;
|
|
1656
|
-
this._endPending = true;
|
|
1657
1774
|
const chunk = typeof chunkOrCallback === "function" ? undefined : chunkOrCallback;
|
|
1658
1775
|
const cb = typeof chunkOrCallback === "function"
|
|
1659
1776
|
? chunkOrCallback
|
|
@@ -1666,18 +1783,7 @@ export class Transform extends EventEmitter {
|
|
|
1666
1783
|
if (chunk !== undefined) {
|
|
1667
1784
|
this._writable.write(chunk);
|
|
1668
1785
|
}
|
|
1669
|
-
|
|
1670
|
-
// processing and data events complete before we close the writable.
|
|
1671
|
-
// Microtasks run before the TransformStream processes data.
|
|
1672
|
-
setTimeout(() => {
|
|
1673
|
-
// Process any writes that occurred during data events
|
|
1674
|
-
for (const { chunk: pendingChunk, callback } of this._pendingEndWrites) {
|
|
1675
|
-
this._writable.write(pendingChunk, callback);
|
|
1676
|
-
}
|
|
1677
|
-
this._pendingEndWrites = [];
|
|
1678
|
-
this._endPending = false;
|
|
1679
|
-
this._writable.end();
|
|
1680
|
-
}, 0);
|
|
1786
|
+
this._scheduleEnd();
|
|
1681
1787
|
return this;
|
|
1682
1788
|
}
|
|
1683
1789
|
/**
|
|
@@ -1687,31 +1793,9 @@ export class Transform extends EventEmitter {
|
|
|
1687
1793
|
return this._readable.read(size);
|
|
1688
1794
|
}
|
|
1689
1795
|
/**
|
|
1690
|
-
* Pipe
|
|
1796
|
+
* Pipe readable side to destination
|
|
1691
1797
|
*/
|
|
1692
1798
|
pipe(destination) {
|
|
1693
|
-
// Mark as having consumer to prevent new auto-consume from starting
|
|
1694
|
-
this._hasDataConsumer = true;
|
|
1695
|
-
// Get the writable target - handle both Transform (with internal _writable) and plain Writable
|
|
1696
|
-
const dest = destination;
|
|
1697
|
-
const target = dest?._writable ?? dest;
|
|
1698
|
-
// Register destination for forwarding
|
|
1699
|
-
this._pipeDestinations.push(target);
|
|
1700
|
-
// If auto-consume is running or has run, we need to handle buffered data ourselves
|
|
1701
|
-
if (this._readableConsuming) {
|
|
1702
|
-
// Forward any buffered data from auto-consume to the destination
|
|
1703
|
-
for (let i = 0; i < this._autoConsumedBuffer.length; i++) {
|
|
1704
|
-
target.write(this._autoConsumedBuffer[i]);
|
|
1705
|
-
}
|
|
1706
|
-
// If auto-consume has ended, end the destination too
|
|
1707
|
-
if (this._autoConsumeEnded) {
|
|
1708
|
-
target.end();
|
|
1709
|
-
}
|
|
1710
|
-
// Don't call _readable.pipe() - auto-consume already consumed _readable
|
|
1711
|
-
// Future data will be forwarded via the 'data' event listener below
|
|
1712
|
-
return destination;
|
|
1713
|
-
}
|
|
1714
|
-
// No auto-consume running - use normal pipe through _readable
|
|
1715
1799
|
return this._readable.pipe(destination);
|
|
1716
1800
|
}
|
|
1717
1801
|
/**
|
|
@@ -1749,6 +1833,10 @@ export class Transform extends EventEmitter {
|
|
|
1749
1833
|
return;
|
|
1750
1834
|
}
|
|
1751
1835
|
this._destroyed = true;
|
|
1836
|
+
if (this._sideForwardingCleanup) {
|
|
1837
|
+
this._sideForwardingCleanup();
|
|
1838
|
+
this._sideForwardingCleanup = null;
|
|
1839
|
+
}
|
|
1752
1840
|
this._readable.destroy(error);
|
|
1753
1841
|
this._writable.destroy(error);
|
|
1754
1842
|
queueMicrotask(() => this.emit("close"));
|
|
@@ -1757,7 +1845,44 @@ export class Transform extends EventEmitter {
|
|
|
1757
1845
|
* Get the underlying Web TransformStream
|
|
1758
1846
|
*/
|
|
1759
1847
|
get webStream() {
|
|
1760
|
-
|
|
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;
|
|
1761
1886
|
}
|
|
1762
1887
|
get readable() {
|
|
1763
1888
|
return this._readable.readable;
|
|
@@ -1799,19 +1924,6 @@ export class Transform extends EventEmitter {
|
|
|
1799
1924
|
* Async iterator support
|
|
1800
1925
|
*/
|
|
1801
1926
|
async *[Symbol.asyncIterator]() {
|
|
1802
|
-
// If auto-consume is running, wait for it to finish and use its buffer
|
|
1803
|
-
if (this._autoConsumePromise) {
|
|
1804
|
-
await this._autoConsumePromise;
|
|
1805
|
-
// Yield all buffered data
|
|
1806
|
-
while (this._autoConsumedBufferIndex < this._autoConsumedBuffer.length) {
|
|
1807
|
-
yield this._autoConsumedBuffer[this._autoConsumedBufferIndex++];
|
|
1808
|
-
}
|
|
1809
|
-
// Reset when drained to avoid prefix growth
|
|
1810
|
-
this._autoConsumedBuffer.length = 0;
|
|
1811
|
-
this._autoConsumedBufferIndex = 0;
|
|
1812
|
-
return;
|
|
1813
|
-
}
|
|
1814
|
-
// Otherwise delegate to readable's iterator
|
|
1815
1927
|
yield* this._readable[Symbol.asyncIterator]();
|
|
1816
1928
|
}
|
|
1817
1929
|
// =========================================================================
|
|
@@ -1822,23 +1934,18 @@ export class Transform extends EventEmitter {
|
|
|
1822
1934
|
*/
|
|
1823
1935
|
static fromWeb(webStream, options) {
|
|
1824
1936
|
const transform = new Transform(options);
|
|
1825
|
-
|
|
1826
|
-
transform._stream = webStream;
|
|
1937
|
+
transform._webStream = webStream;
|
|
1827
1938
|
// Replace internal streams with the ones from the web stream
|
|
1828
1939
|
const newReadable = Readable.fromWeb(webStream.readable, { objectMode: options?.objectMode });
|
|
1829
1940
|
const newWritable = Writable.fromWeb(webStream.writable, { objectMode: options?.objectMode });
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1941
|
+
if (transform._sideForwardingCleanup) {
|
|
1942
|
+
transform._sideForwardingCleanup();
|
|
1943
|
+
transform._sideForwardingCleanup = null;
|
|
1944
|
+
}
|
|
1833
1945
|
transform._readable = newReadable;
|
|
1834
1946
|
transform._writable = newWritable;
|
|
1835
|
-
// Re-connect event forwarding
|
|
1836
|
-
|
|
1837
|
-
newReadable.on("end", () => transform.emit("end"));
|
|
1838
|
-
newReadable.on("error", (err) => transform.emit("error", err));
|
|
1839
|
-
newWritable.on("finish", () => transform.emit("finish"));
|
|
1840
|
-
newWritable.on("drain", () => transform.emit("drain"));
|
|
1841
|
-
newWritable.on("error", (err) => transform.emit("error", err));
|
|
1947
|
+
// Re-connect event forwarding (data forwarding remains lazy via Transform.on)
|
|
1948
|
+
transform._setupSideForwarding();
|
|
1842
1949
|
return transform;
|
|
1843
1950
|
}
|
|
1844
1951
|
/**
|
|
@@ -1894,7 +2001,13 @@ export class Duplex extends EventEmitter {
|
|
|
1894
2001
|
callback();
|
|
1895
2002
|
}
|
|
1896
2003
|
});
|
|
1897
|
-
|
|
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 });
|
|
1898
2011
|
readable.pipe(sink);
|
|
1899
2012
|
};
|
|
1900
2013
|
// If it has readable and/or writable properties
|
|
@@ -1902,21 +2015,25 @@ export class Duplex extends EventEmitter {
|
|
|
1902
2015
|
source !== null &&
|
|
1903
2016
|
"readable" in source &&
|
|
1904
2017
|
"writable" in source) {
|
|
1905
|
-
const duplex = new Duplex();
|
|
1906
2018
|
const pair = source;
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
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) => {
|
|
1914
2026
|
pair.writable.write(chunk, encoding, callback);
|
|
1915
|
-
}
|
|
1916
|
-
|
|
2027
|
+
}
|
|
2028
|
+
: undefined,
|
|
2029
|
+
final: pair.writable
|
|
2030
|
+
? callback => {
|
|
1917
2031
|
pair.writable.end(callback);
|
|
1918
2032
|
}
|
|
1919
|
-
|
|
2033
|
+
: undefined
|
|
2034
|
+
});
|
|
2035
|
+
if (pair.readable) {
|
|
2036
|
+
forwardReadableToDuplex(pair.readable, duplex);
|
|
1920
2037
|
}
|
|
1921
2038
|
return duplex;
|
|
1922
2039
|
}
|
|
@@ -1954,9 +2071,22 @@ export class Duplex extends EventEmitter {
|
|
|
1954
2071
|
*/
|
|
1955
2072
|
static fromWeb(pair, options) {
|
|
1956
2073
|
const duplex = new Duplex(options);
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
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();
|
|
1960
2090
|
return duplex;
|
|
1961
2091
|
}
|
|
1962
2092
|
/**
|
|
@@ -1972,6 +2102,7 @@ export class Duplex extends EventEmitter {
|
|
|
1972
2102
|
super();
|
|
1973
2103
|
// Track if we've already set up data forwarding
|
|
1974
2104
|
this._dataForwardingSetup = false;
|
|
2105
|
+
this._sideForwardingCleanup = null;
|
|
1975
2106
|
this.allowHalfOpen = options?.allowHalfOpen ?? true;
|
|
1976
2107
|
// Support shorthand objectMode option
|
|
1977
2108
|
const objectMode = options?.objectMode ?? false;
|
|
@@ -1988,23 +2119,31 @@ export class Duplex extends EventEmitter {
|
|
|
1988
2119
|
write: options?.write?.bind(this),
|
|
1989
2120
|
final: options?.final?.bind(this)
|
|
1990
2121
|
});
|
|
2122
|
+
this._setupSideForwarding();
|
|
2123
|
+
}
|
|
2124
|
+
_setupSideForwarding() {
|
|
2125
|
+
if (this._sideForwardingCleanup) {
|
|
2126
|
+
this._sideForwardingCleanup();
|
|
2127
|
+
this._sideForwardingCleanup = null;
|
|
2128
|
+
}
|
|
2129
|
+
const registry = createListenerRegistry();
|
|
1991
2130
|
// Forward non-data events (data forwarding is lazy to avoid premature flowing)
|
|
1992
|
-
this._readable
|
|
2131
|
+
registry.once(this._readable, "end", () => {
|
|
1993
2132
|
this.emit("end");
|
|
1994
|
-
// If not allowHalfOpen, end the writable side too
|
|
1995
2133
|
if (!this.allowHalfOpen) {
|
|
1996
2134
|
this._writable.end();
|
|
1997
2135
|
}
|
|
1998
2136
|
});
|
|
1999
|
-
this._readable
|
|
2000
|
-
this._writable
|
|
2001
|
-
this._writable
|
|
2002
|
-
this._writable
|
|
2003
|
-
|
|
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", () => {
|
|
2004
2142
|
if (!this.allowHalfOpen && !this._readable.destroyed) {
|
|
2005
2143
|
this._readable.destroy();
|
|
2006
2144
|
}
|
|
2007
2145
|
});
|
|
2146
|
+
this._sideForwardingCleanup = () => registry.cleanup();
|
|
2008
2147
|
}
|
|
2009
2148
|
/**
|
|
2010
2149
|
* Override on() to set up data forwarding lazily
|
|
@@ -2123,6 +2262,10 @@ export class Duplex extends EventEmitter {
|
|
|
2123
2262
|
* Destroy both sides
|
|
2124
2263
|
*/
|
|
2125
2264
|
destroy(error) {
|
|
2265
|
+
if (this._sideForwardingCleanup) {
|
|
2266
|
+
this._sideForwardingCleanup();
|
|
2267
|
+
this._sideForwardingCleanup = null;
|
|
2268
|
+
}
|
|
2126
2269
|
this._readable.destroy(error);
|
|
2127
2270
|
this._writable.destroy(error);
|
|
2128
2271
|
return this;
|
|
@@ -2263,21 +2406,8 @@ export class Collector extends Writable {
|
|
|
2263
2406
|
// =============================================================================
|
|
2264
2407
|
// PullStream / BufferedStream / DataChunk helpers
|
|
2265
2408
|
// =============================================================================
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
constructor(options) {
|
|
2269
|
-
super(options);
|
|
2270
|
-
}
|
|
2271
|
-
}
|
|
2272
|
-
export class StringChunk extends StandaloneStringChunk {
|
|
2273
|
-
}
|
|
2274
|
-
export class BufferChunk extends StandaloneBufferChunk {
|
|
2275
|
-
}
|
|
2276
|
-
export class BufferedStream extends StandaloneBufferedStream {
|
|
2277
|
-
constructor(options) {
|
|
2278
|
-
super(options);
|
|
2279
|
-
}
|
|
2280
|
-
}
|
|
2409
|
+
// Standalone cross-platform helpers
|
|
2410
|
+
export { PullStream, BufferedStream, StringChunk, BufferChunk };
|
|
2281
2411
|
// =============================================================================
|
|
2282
2412
|
// Stream Creation Functions
|
|
2283
2413
|
// =============================================================================
|
|
@@ -2285,36 +2415,16 @@ export class BufferedStream extends StandaloneBufferedStream {
|
|
|
2285
2415
|
* Create a readable stream with custom read implementation
|
|
2286
2416
|
*/
|
|
2287
2417
|
export function createReadable(options) {
|
|
2288
|
-
|
|
2289
|
-
//
|
|
2290
|
-
|
|
2291
|
-
const originalRead = readable.read.bind(readable);
|
|
2292
|
-
readable.read = function (size) {
|
|
2293
|
-
options.read(size ?? 16384);
|
|
2294
|
-
return originalRead(size);
|
|
2295
|
-
};
|
|
2296
|
-
}
|
|
2297
|
-
return readable;
|
|
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);
|
|
2298
2421
|
}
|
|
2299
2422
|
/**
|
|
2300
2423
|
* Create a readable stream from an async iterable
|
|
2301
2424
|
*/
|
|
2302
2425
|
export function createReadableFromAsyncIterable(iterable, options) {
|
|
2303
2426
|
const readable = new Readable({ ...options, objectMode: options?.objectMode ?? true });
|
|
2304
|
-
(
|
|
2305
|
-
try {
|
|
2306
|
-
for await (const chunk of iterable) {
|
|
2307
|
-
if (!readable.push(chunk)) {
|
|
2308
|
-
// Backpressure - wait a bit
|
|
2309
|
-
await new Promise(resolve => setTimeout(resolve, 0));
|
|
2310
|
-
}
|
|
2311
|
-
}
|
|
2312
|
-
readable.push(null);
|
|
2313
|
-
}
|
|
2314
|
-
catch (err) {
|
|
2315
|
-
readable.destroy(err);
|
|
2316
|
-
}
|
|
2317
|
-
})();
|
|
2427
|
+
pumpAsyncIterableToReadable(readable, iterable);
|
|
2318
2428
|
return readable;
|
|
2319
2429
|
}
|
|
2320
2430
|
/**
|
|
@@ -2343,38 +2453,8 @@ export function createReadableFromArray(data, options) {
|
|
|
2343
2453
|
* Create a writable stream with custom write implementation
|
|
2344
2454
|
*/
|
|
2345
2455
|
export function createWritable(options) {
|
|
2346
|
-
//
|
|
2347
|
-
|
|
2348
|
-
write: async (chunk) => {
|
|
2349
|
-
if (options?.write) {
|
|
2350
|
-
return new Promise((resolve, reject) => {
|
|
2351
|
-
options.write(chunk, "utf8", err => {
|
|
2352
|
-
if (err) {
|
|
2353
|
-
reject(err);
|
|
2354
|
-
}
|
|
2355
|
-
else {
|
|
2356
|
-
resolve();
|
|
2357
|
-
}
|
|
2358
|
-
});
|
|
2359
|
-
});
|
|
2360
|
-
}
|
|
2361
|
-
},
|
|
2362
|
-
close: async () => {
|
|
2363
|
-
if (options?.final) {
|
|
2364
|
-
return new Promise((resolve, reject) => {
|
|
2365
|
-
options.final(err => {
|
|
2366
|
-
if (err) {
|
|
2367
|
-
reject(err);
|
|
2368
|
-
}
|
|
2369
|
-
else {
|
|
2370
|
-
resolve();
|
|
2371
|
-
}
|
|
2372
|
-
});
|
|
2373
|
-
});
|
|
2374
|
-
}
|
|
2375
|
-
}
|
|
2376
|
-
});
|
|
2377
|
-
return new Writable({ ...options, stream });
|
|
2456
|
+
// Writable already supports Node-style `write()` / `final()` via the constructor.
|
|
2457
|
+
return new Writable(options);
|
|
2378
2458
|
}
|
|
2379
2459
|
/**
|
|
2380
2460
|
* Create a transform stream from a transform function
|
|
@@ -2410,20 +2490,6 @@ export function createPullStream(options) {
|
|
|
2410
2490
|
export function createBufferedStream(options) {
|
|
2411
2491
|
return new BufferedStream(options);
|
|
2412
2492
|
}
|
|
2413
|
-
const isReadableStream = (value) => !!value && typeof value === "object" && typeof value.getReader === "function";
|
|
2414
|
-
const isAsyncIterable = (value) => {
|
|
2415
|
-
if (!value || (typeof value !== "object" && typeof value !== "function")) {
|
|
2416
|
-
return false;
|
|
2417
|
-
}
|
|
2418
|
-
return typeof value[Symbol.asyncIterator] === "function";
|
|
2419
|
-
};
|
|
2420
|
-
const isWritableStream = (value) => !!value && typeof value === "object" && typeof value.getWriter === "function";
|
|
2421
|
-
const isTransformStream = (value) => !!value &&
|
|
2422
|
-
typeof value === "object" &&
|
|
2423
|
-
!!value.readable &&
|
|
2424
|
-
!!value.writable &&
|
|
2425
|
-
isReadableStream(value.readable) &&
|
|
2426
|
-
isWritableStream(value.writable);
|
|
2427
2493
|
const isPipelineOptions = (value) => {
|
|
2428
2494
|
if (!value || typeof value !== "object") {
|
|
2429
2495
|
return false;
|
|
@@ -2509,11 +2575,17 @@ export function pipeline(...args) {
|
|
|
2509
2575
|
const transforms = normalized.slice(1, -1);
|
|
2510
2576
|
let completed = false;
|
|
2511
2577
|
const allStreams = [source, ...transforms, destination];
|
|
2512
|
-
const
|
|
2578
|
+
const registry = createListenerRegistry();
|
|
2579
|
+
let onAbort;
|
|
2580
|
+
const cleanupWithSignal = (error) => {
|
|
2513
2581
|
if (completed) {
|
|
2514
2582
|
return;
|
|
2515
2583
|
}
|
|
2516
2584
|
completed = true;
|
|
2585
|
+
registry.cleanup();
|
|
2586
|
+
if (onAbort && options.signal) {
|
|
2587
|
+
options.signal.removeEventListener("abort", onAbort);
|
|
2588
|
+
}
|
|
2517
2589
|
// Destroy all streams on error
|
|
2518
2590
|
if (error) {
|
|
2519
2591
|
for (const stream of allStreams) {
|
|
@@ -2530,12 +2602,11 @@ export function pipeline(...args) {
|
|
|
2530
2602
|
// Handle abort signal
|
|
2531
2603
|
if (options.signal) {
|
|
2532
2604
|
if (options.signal.aborted) {
|
|
2533
|
-
|
|
2605
|
+
cleanupWithSignal(new Error("Pipeline aborted"));
|
|
2534
2606
|
return;
|
|
2535
2607
|
}
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
});
|
|
2608
|
+
onAbort = () => cleanupWithSignal(new Error("Pipeline aborted"));
|
|
2609
|
+
options.signal.addEventListener("abort", onAbort);
|
|
2539
2610
|
}
|
|
2540
2611
|
// Chain the streams
|
|
2541
2612
|
let current = source;
|
|
@@ -2549,13 +2620,35 @@ export function pipeline(...args) {
|
|
|
2549
2620
|
}
|
|
2550
2621
|
else {
|
|
2551
2622
|
// Don't end destination
|
|
2552
|
-
|
|
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);
|
|
2553
2646
|
}
|
|
2554
2647
|
// Handle completion
|
|
2555
|
-
|
|
2648
|
+
registry.once(destination, "finish", () => cleanupWithSignal());
|
|
2556
2649
|
// Handle errors on all streams
|
|
2557
2650
|
for (const stream of allStreams) {
|
|
2558
|
-
|
|
2651
|
+
registry.once(stream, "error", (err) => cleanupWithSignal(err));
|
|
2559
2652
|
}
|
|
2560
2653
|
});
|
|
2561
2654
|
// If callback provided, use it
|
|
@@ -2595,11 +2688,20 @@ export function finished(stream, optionsOrCallback, callback) {
|
|
|
2595
2688
|
const promise = new Promise((resolve, reject) => {
|
|
2596
2689
|
const normalizedStream = toBrowserPipelineStream(stream);
|
|
2597
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
|
+
};
|
|
2598
2699
|
const done = (err) => {
|
|
2599
2700
|
if (resolved) {
|
|
2600
2701
|
return;
|
|
2601
2702
|
}
|
|
2602
2703
|
resolved = true;
|
|
2704
|
+
cleanup();
|
|
2603
2705
|
if (err && !options.error) {
|
|
2604
2706
|
reject(err);
|
|
2605
2707
|
}
|
|
@@ -2613,9 +2715,8 @@ export function finished(stream, optionsOrCallback, callback) {
|
|
|
2613
2715
|
done(new Error("Aborted"));
|
|
2614
2716
|
return;
|
|
2615
2717
|
}
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
});
|
|
2718
|
+
onAbort = () => done(new Error("Aborted"));
|
|
2719
|
+
options.signal.addEventListener("abort", onAbort);
|
|
2619
2720
|
}
|
|
2620
2721
|
const checkReadable = options.readable !== false;
|
|
2621
2722
|
const checkWritable = options.writable !== false;
|
|
@@ -2630,13 +2731,13 @@ export function finished(stream, optionsOrCallback, callback) {
|
|
|
2630
2731
|
}
|
|
2631
2732
|
// Listen for events
|
|
2632
2733
|
if (checkWritable) {
|
|
2633
|
-
|
|
2734
|
+
registry.once(normalizedStream, "finish", () => done());
|
|
2634
2735
|
}
|
|
2635
2736
|
if (checkReadable) {
|
|
2636
|
-
|
|
2737
|
+
registry.once(normalizedStream, "end", () => done());
|
|
2637
2738
|
}
|
|
2638
|
-
|
|
2639
|
-
|
|
2739
|
+
registry.once(normalizedStream, "error", (err) => done(err));
|
|
2740
|
+
registry.once(normalizedStream, "close", () => done());
|
|
2640
2741
|
});
|
|
2641
2742
|
// If callback provided, use it
|
|
2642
2743
|
if (cb) {
|
|
@@ -2658,38 +2759,8 @@ export async function streamToPromise(stream) {
|
|
|
2658
2759
|
* (Browser equivalent of Node.js streamToBuffer)
|
|
2659
2760
|
*/
|
|
2660
2761
|
export async function streamToUint8Array(stream) {
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
iterable = Readable.fromWeb(stream);
|
|
2664
|
-
}
|
|
2665
|
-
else if (isAsyncIterable(stream)) {
|
|
2666
|
-
iterable = stream;
|
|
2667
|
-
}
|
|
2668
|
-
else {
|
|
2669
|
-
throw new Error("streamToUint8Array: unsupported stream type");
|
|
2670
|
-
}
|
|
2671
|
-
const chunks = [];
|
|
2672
|
-
let totalLength = 0;
|
|
2673
|
-
for await (const chunk of iterable) {
|
|
2674
|
-
chunks.push(chunk);
|
|
2675
|
-
totalLength += chunk.length;
|
|
2676
|
-
}
|
|
2677
|
-
// Fast paths
|
|
2678
|
-
const len = chunks.length;
|
|
2679
|
-
if (len === 0) {
|
|
2680
|
-
return new Uint8Array(0);
|
|
2681
|
-
}
|
|
2682
|
-
if (len === 1) {
|
|
2683
|
-
return chunks[0];
|
|
2684
|
-
}
|
|
2685
|
-
// Use precalculated total length
|
|
2686
|
-
const result = new Uint8Array(totalLength);
|
|
2687
|
-
let offset = 0;
|
|
2688
|
-
for (let i = 0; i < len; i++) {
|
|
2689
|
-
result.set(chunks[i], offset);
|
|
2690
|
-
offset += chunks[i].length;
|
|
2691
|
-
}
|
|
2692
|
-
return result;
|
|
2762
|
+
const [chunks, totalLength] = await collectStreamChunks(stream);
|
|
2763
|
+
return concatUint8Arrays(chunks, totalLength);
|
|
2693
2764
|
}
|
|
2694
2765
|
/**
|
|
2695
2766
|
* Alias for streamToUint8Array (Node.js compatibility)
|
|
@@ -2699,23 +2770,16 @@ export const streamToBuffer = streamToUint8Array;
|
|
|
2699
2770
|
* Collect all data from a readable stream into a string
|
|
2700
2771
|
*/
|
|
2701
2772
|
export async function streamToString(stream, encoding) {
|
|
2702
|
-
const
|
|
2703
|
-
|
|
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);
|
|
2704
2777
|
}
|
|
2705
2778
|
/**
|
|
2706
2779
|
* Drain a stream (consume all data without processing)
|
|
2707
2780
|
*/
|
|
2708
2781
|
export async function drainStream(stream) {
|
|
2709
|
-
|
|
2710
|
-
if (isReadableStream(stream)) {
|
|
2711
|
-
iterable = Readable.fromWeb(stream);
|
|
2712
|
-
}
|
|
2713
|
-
else if (isAsyncIterable(stream)) {
|
|
2714
|
-
iterable = stream;
|
|
2715
|
-
}
|
|
2716
|
-
else {
|
|
2717
|
-
throw new Error("drainStream: unsupported stream type");
|
|
2718
|
-
}
|
|
2782
|
+
const iterable = toReadableAsyncIterable(stream, "drainStream");
|
|
2719
2783
|
for await (const _chunk of iterable) {
|
|
2720
2784
|
// Consume data
|
|
2721
2785
|
}
|
|
@@ -2788,14 +2852,19 @@ export function addAbortSignal(signal, stream) {
|
|
|
2788
2852
|
stream.destroy(new Error("Aborted"));
|
|
2789
2853
|
return stream;
|
|
2790
2854
|
}
|
|
2855
|
+
const cleanup = () => {
|
|
2856
|
+
signal.removeEventListener("abort", onAbort);
|
|
2857
|
+
removeEmitterListener(stream, "close", onClose);
|
|
2858
|
+
};
|
|
2791
2859
|
const onAbort = () => {
|
|
2860
|
+
cleanup();
|
|
2792
2861
|
stream.destroy(new Error("Aborted"));
|
|
2793
2862
|
};
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
});
|
|
2863
|
+
const onClose = () => {
|
|
2864
|
+
cleanup();
|
|
2865
|
+
};
|
|
2866
|
+
signal.addEventListener("abort", onAbort);
|
|
2867
|
+
addEmitterListener(stream, "close", onClose, { once: true });
|
|
2799
2868
|
return stream;
|
|
2800
2869
|
}
|
|
2801
2870
|
/**
|
|
@@ -2804,60 +2873,68 @@ export function addAbortSignal(signal, stream) {
|
|
|
2804
2873
|
export function createDuplex(options) {
|
|
2805
2874
|
const readableObjectMode = options?.readableObjectMode ?? options?.objectMode;
|
|
2806
2875
|
const writableObjectMode = options?.writableObjectMode ?? options?.objectMode;
|
|
2876
|
+
const underlyingWritable = options?.writable;
|
|
2807
2877
|
const duplex = new Duplex({
|
|
2808
2878
|
allowHalfOpen: options?.allowHalfOpen,
|
|
2809
2879
|
readableHighWaterMark: options?.readableHighWaterMark,
|
|
2810
2880
|
writableHighWaterMark: options?.writableHighWaterMark,
|
|
2811
2881
|
readableObjectMode,
|
|
2812
|
-
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)
|
|
2813
2913
|
});
|
|
2814
|
-
// If
|
|
2914
|
+
// If an underlying readable is provided, forward it into the duplex readable side.
|
|
2815
2915
|
if (options?.readable) {
|
|
2816
2916
|
const readable = options.readable;
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
duplex.end = function (chunkOrCallback, encodingOrCallback, callback) {
|
|
2839
|
-
const cb = typeof chunkOrCallback === "function"
|
|
2840
|
-
? chunkOrCallback
|
|
2841
|
-
: typeof encodingOrCallback === "function"
|
|
2842
|
-
? encodingOrCallback
|
|
2843
|
-
: (callback ?? (() => { }));
|
|
2844
|
-
if (chunkOrCallback !== undefined && typeof chunkOrCallback !== "function") {
|
|
2845
|
-
duplex.write(chunkOrCallback);
|
|
2846
|
-
}
|
|
2847
|
-
// Call custom final handler
|
|
2848
|
-
options.final.call(duplex, (err) => {
|
|
2849
|
-
if (err) {
|
|
2850
|
-
duplex.emit("error", err);
|
|
2851
|
-
}
|
|
2852
|
-
else {
|
|
2853
|
-
duplex.emit("finish");
|
|
2854
|
-
}
|
|
2855
|
-
// Call original end to properly close writable side
|
|
2856
|
-
originalEnd();
|
|
2857
|
-
cb();
|
|
2858
|
-
});
|
|
2859
|
-
return duplex;
|
|
2860
|
-
};
|
|
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);
|
|
2861
2938
|
}
|
|
2862
2939
|
if (options?.destroy) {
|
|
2863
2940
|
const originalDestroy = duplex.destroy.bind(duplex);
|
|
@@ -2881,20 +2958,7 @@ export function createDuplex(options) {
|
|
|
2881
2958
|
*/
|
|
2882
2959
|
export function createReadableFromGenerator(generator, options) {
|
|
2883
2960
|
const readable = new Readable({ ...options, objectMode: options?.objectMode ?? true });
|
|
2884
|
-
(
|
|
2885
|
-
try {
|
|
2886
|
-
for await (const chunk of generator()) {
|
|
2887
|
-
if (!readable.push(chunk)) {
|
|
2888
|
-
// Backpressure
|
|
2889
|
-
await new Promise(resolve => setTimeout(resolve, 0));
|
|
2890
|
-
}
|
|
2891
|
-
}
|
|
2892
|
-
readable.push(null);
|
|
2893
|
-
}
|
|
2894
|
-
catch (err) {
|
|
2895
|
-
readable.destroy(err);
|
|
2896
|
-
}
|
|
2897
|
-
})();
|
|
2961
|
+
pumpAsyncIterableToReadable(readable, generator());
|
|
2898
2962
|
return readable;
|
|
2899
2963
|
}
|
|
2900
2964
|
/**
|
|
@@ -2924,8 +2988,8 @@ export function compose(...transforms) {
|
|
|
2924
2988
|
transform: chunk => chunk
|
|
2925
2989
|
});
|
|
2926
2990
|
}
|
|
2927
|
-
|
|
2928
|
-
if (len === 1
|
|
2991
|
+
// Preserve identity: compose(single) returns the same transform.
|
|
2992
|
+
if (len === 1) {
|
|
2929
2993
|
return transforms[0];
|
|
2930
2994
|
}
|
|
2931
2995
|
// Chain the transforms: first → second → ... → last
|
|
@@ -2935,74 +2999,118 @@ export function compose(...transforms) {
|
|
|
2935
2999
|
for (let i = 0; i < len - 1; i++) {
|
|
2936
3000
|
transforms[i].pipe(transforms[i + 1]);
|
|
2937
3001
|
}
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
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;
|
|
2943
3025
|
}
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
this._endForwarding = true;
|
|
2951
|
-
last.on("end", () => this.emit("end"));
|
|
2952
|
-
}
|
|
2953
|
-
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;
|
|
2954
3032
|
}
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
3033
|
+
forwardEnd = true;
|
|
3034
|
+
registry.once(last, "end", () => composed.emit("end"));
|
|
3035
|
+
};
|
|
3036
|
+
const ensureReadableForwarding = () => {
|
|
3037
|
+
if (forwardReadable) {
|
|
3038
|
+
return;
|
|
2960
3039
|
}
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
}
|
|
2970
|
-
first.end(chunkOrCallback, encodingOrCallback, callback);
|
|
2971
|
-
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();
|
|
2972
3048
|
}
|
|
2973
|
-
|
|
2974
|
-
|
|
3049
|
+
else if (event === "end") {
|
|
3050
|
+
ensureEndForwarding();
|
|
2975
3051
|
}
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
t.destroy(error);
|
|
2979
|
-
}
|
|
2980
|
-
super.destroy(error);
|
|
3052
|
+
else if (event === "readable") {
|
|
3053
|
+
ensureReadableForwarding();
|
|
2981
3054
|
}
|
|
2982
|
-
|
|
2983
|
-
|
|
3055
|
+
return originalOn(event, listener);
|
|
3056
|
+
};
|
|
3057
|
+
composed.once = (event, listener) => {
|
|
3058
|
+
if (event === "data") {
|
|
3059
|
+
ensureDataForwarding();
|
|
2984
3060
|
}
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
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;
|
|
2992
3101
|
}
|
|
2993
|
-
|
|
3102
|
+
return;
|
|
2994
3103
|
}
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
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);
|
|
2999
3113
|
});
|
|
3000
|
-
// Forward errors from any transform
|
|
3001
|
-
for (const t of transforms) {
|
|
3002
|
-
t.on("error", (err) => {
|
|
3003
|
-
composed.emit("error", err);
|
|
3004
|
-
});
|
|
3005
|
-
}
|
|
3006
3114
|
// Reflect underlying readability/writability like the previous duck-typed wrapper
|
|
3007
3115
|
Object.defineProperty(composed, "readable", {
|
|
3008
3116
|
get: () => last.readable
|
|
@@ -3248,77 +3356,54 @@ export function duplexPair(options) {
|
|
|
3248
3356
|
async function collectStreamChunks(stream) {
|
|
3249
3357
|
const chunks = [];
|
|
3250
3358
|
let totalLength = 0;
|
|
3251
|
-
|
|
3252
|
-
if (isReadableStream(stream)) {
|
|
3253
|
-
iterable = Readable.fromWeb(stream);
|
|
3254
|
-
}
|
|
3255
|
-
else if (isAsyncIterable(stream)) {
|
|
3256
|
-
iterable = stream;
|
|
3257
|
-
}
|
|
3258
|
-
else {
|
|
3259
|
-
throw new Error("collectStreamChunks: unsupported stream type");
|
|
3260
|
-
}
|
|
3359
|
+
const iterable = toReadableAsyncIterable(stream, "collectStreamChunks");
|
|
3261
3360
|
for await (const chunk of iterable) {
|
|
3262
3361
|
chunks.push(chunk);
|
|
3263
3362
|
totalLength += chunk.length;
|
|
3264
3363
|
}
|
|
3265
|
-
return
|
|
3364
|
+
return [chunks, totalLength];
|
|
3266
3365
|
}
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
if (len === 0) {
|
|
3271
|
-
return new Uint8Array(0);
|
|
3272
|
-
}
|
|
3273
|
-
if (len === 1) {
|
|
3274
|
-
return chunks[0];
|
|
3366
|
+
function toReadableAsyncIterable(stream, name) {
|
|
3367
|
+
if (isReadableStream(stream)) {
|
|
3368
|
+
return Readable.fromWeb(stream);
|
|
3275
3369
|
}
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
for (let i = 0; i < len; i++) {
|
|
3279
|
-
result.set(chunks[i], offset);
|
|
3280
|
-
offset += chunks[i].length;
|
|
3370
|
+
if (isAsyncIterable(stream)) {
|
|
3371
|
+
return stream;
|
|
3281
3372
|
}
|
|
3282
|
-
|
|
3373
|
+
throw new Error(`${name}: unsupported stream type`);
|
|
3283
3374
|
}
|
|
3284
3375
|
export const consumers = {
|
|
3285
3376
|
/**
|
|
3286
3377
|
* Consume entire stream as ArrayBuffer
|
|
3287
3378
|
*/
|
|
3288
3379
|
async arrayBuffer(stream) {
|
|
3289
|
-
const
|
|
3290
|
-
|
|
3291
|
-
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);
|
|
3292
3382
|
},
|
|
3293
3383
|
/**
|
|
3294
3384
|
* Consume entire stream as Blob
|
|
3295
3385
|
*/
|
|
3296
3386
|
async blob(stream, options) {
|
|
3297
|
-
const
|
|
3387
|
+
const [chunks] = await collectStreamChunks(stream);
|
|
3298
3388
|
return new Blob(chunks, options);
|
|
3299
3389
|
},
|
|
3300
3390
|
/**
|
|
3301
3391
|
* Consume entire stream as Buffer (Uint8Array in browser)
|
|
3302
3392
|
*/
|
|
3303
3393
|
async buffer(stream) {
|
|
3304
|
-
|
|
3305
|
-
return concatWithLength(chunks, totalLength);
|
|
3394
|
+
return streamToUint8Array(stream);
|
|
3306
3395
|
},
|
|
3307
3396
|
/**
|
|
3308
3397
|
* Consume entire stream as JSON
|
|
3309
3398
|
*/
|
|
3310
3399
|
async json(stream) {
|
|
3311
|
-
|
|
3312
|
-
return JSON.parse(text);
|
|
3400
|
+
return JSON.parse(await streamToString(stream));
|
|
3313
3401
|
},
|
|
3314
3402
|
/**
|
|
3315
3403
|
* Consume entire stream as text
|
|
3316
3404
|
*/
|
|
3317
3405
|
async text(stream, encoding) {
|
|
3318
|
-
|
|
3319
|
-
const combined = concatWithLength(chunks, totalLength);
|
|
3320
|
-
const decoder = encoding ? getTextDecoder(encoding) : textDecoder;
|
|
3321
|
-
return decoder.decode(combined);
|
|
3406
|
+
return streamToString(stream, encoding);
|
|
3322
3407
|
}
|
|
3323
3408
|
};
|
|
3324
3409
|
// =============================================================================
|