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