├── .codeclimate.yml ├── .editorconfig ├── .gitattributes ├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING ├── FUNDING.yml ├── ISSUE_TEMPLATE ├── PULL_REQUEST_TEMPLATE └── workflows │ └── test.yml ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── API.md ├── CHANGELOG.md ├── LICENSE ├── README.md ├── eslint.config.js ├── package-lock.json ├── package.json ├── src ├── index.test.ts └── index.ts └── tsconfig.json /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by a `metapak` 2 | # module. Do not change it here, your changes would 3 | # be overridden. 4 | 5 | engines: 6 | eslint: 7 | enabled: true 8 | 9 | ratings: 10 | paths: 11 | - "'src/**/*.ts'" 12 | ## Exclude test files. 13 | exclude_patterns: 14 | - "dist/" 15 | - "**/node_modules/" 16 | - "src/**/*.tests.ts" 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by a `metapak` 2 | # module. Do not change it elsewhere, changes would 3 | # be overridden. 4 | 5 | # EditorConfig is awesome: http://EditorConfig.org 6 | 7 | # top-most EditorConfig file 8 | root = true 9 | 10 | # Unix-style newlines with a newline ending every file 11 | [*] 12 | end_of_line = lf 13 | insert_final_newline = true 14 | 15 | # Matches multiple files with brace expansion notation 16 | # Set default charset 17 | # 2 space indentation 18 | [*.{js,css}] 19 | charset = utf-8 20 | indent_style = space 21 | trim_trailing_whitespace = true 22 | indent_size = 2 23 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Enforce Unix newlines 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | Be kind, except if I behave like an asshole, if so, tell me by linking to this 4 | file. 5 | 6 | I try hard to document and automate things so that you cannot create noises 7 | without really willing to do so. 8 | 9 | This is why I'll just delete issues/comments making be sad. 10 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING: -------------------------------------------------------------------------------- 1 | Contributing to this project requires you to be 2 | a gentleman. 3 | 4 | By contributing you must agree with publishing your 5 | changes into the same license that apply to the current code. 6 | 7 | You will find the license in the LICENSE file at 8 | the root of this repository. 9 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [nfroidure] 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE: -------------------------------------------------------------------------------- 1 | ## Issue 2 | 16 | 17 | I'm a gentledev I: 18 | - [ ] fully read the README recently 19 | - [ ] searched for existing issues 20 | - [ ] checked I'm up to date with the latest version of the project 21 | 22 | ### Expected behavior 23 | 24 | ### Actual behavior 25 | 26 | ### Steps to reproduce the behavior 27 | 28 | ### Debugging informations 29 | - `node -v` result: 30 | ``` 31 | 32 | ``` 33 | 34 | - `npm -v` result: 35 | ``` 36 | 37 | ``` 38 | If the result is lower than 20.11.1, there is 39 | poor chances I even have a look to it. Please, 40 | use the last [NodeJS LTS version](https://nodejs.org/en/). 41 | 42 | ## Feature request 43 | 54 | 55 | ### Feature description 56 | 57 | ### Use cases 58 | 59 | - [ ] I will/did implement the feature 60 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE: -------------------------------------------------------------------------------- 1 | 10 | 11 | Fixes # 12 | 13 | ### Proposed changes 14 | - 15 | - 16 | 17 | 18 | 19 | ### Code quality 20 | - [ ] I made some tests for my changes 21 | - [ ] I added my name in the 22 | [contributors](https://docs.npmjs.com/files/package.json#people-fields-author-contributors) 23 | field of the `package.json` file. Beware to use the same format than for the author field 24 | for the entries so that you'll get a mention in the `README.md` with a link to your website. 25 | 26 | ### License 27 | To get your contribution merged, you must check the following. 28 | 29 | - [ ] I read the project license in the LICENSE file 30 | - [ ] I agree with publishing under this project license 31 | 32 | 46 | ### Join 47 | - [ ] I wish to join the core team 48 | - [ ] I agree that with great powers comes responsibilities 49 | - [ ] I'm a nice person 50 | 51 | My NPM username: 52 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by a `metapak` 2 | # module. Do not change it here, your changes would 3 | # be overridden. 4 | 5 | name: Node.js CI 6 | 7 | on: 8 | push: 9 | branches: [main] 10 | pull_request: 11 | branches: [main] 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [20.x] 20 | 21 | steps: 22 | - uses: actions/checkout@v4 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | cache: 'npm' 28 | - name: Install dependencies 29 | run: npm ci 30 | - name: Run pre-commit tests 31 | run: npm run precz 32 | - name: Run coverage 33 | run: npm run cover 34 | - name: Report Coveralls 35 | uses: coverallsapp/github-action@v2 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by a `metapak` 2 | # module. Do not change it elsewhere, changes would 3 | # be overridden. 4 | 5 | # Created by https://www.gitignore.io/api/osx,node,linux 6 | 7 | ### Linux ### 8 | *~ 9 | 10 | # temporary files which can be created if a process still has a handle open of a deleted file 11 | .fuse_hidden* 12 | 13 | # KDE directory preferences 14 | .directory 15 | 16 | # Linux trash folder which might appear on any partition or disk 17 | .Trash-* 18 | 19 | # .nfs files are created when an open file is removed but is still being accessed 20 | .nfs* 21 | 22 | ### Node ### 23 | # Logs 24 | logs 25 | *.log 26 | npm-debug.log* 27 | yarn-debug.log* 28 | yarn-error.log* 29 | 30 | # Runtime data 31 | pids 32 | *.pid 33 | *.seed 34 | *.pid.lock 35 | 36 | # Directory for instrumented libs generated by jscoverage/JSCover 37 | lib-cov 38 | 39 | # Coverage directory used by tools like istanbul 40 | coverage 41 | 42 | # nyc test coverage 43 | .nyc_output 44 | 45 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 46 | .grunt 47 | 48 | # Bower dependency directory (https://bower.io/) 49 | bower_components 50 | 51 | # node-waf configuration 52 | .lock-wscript 53 | 54 | # Compiled binary addons (https://nodejs.org/api/addons.html) 55 | build/Release 56 | 57 | # Dependency directories 58 | node_modules/ 59 | jspm_packages/ 60 | 61 | # TypeScript v1 declaration files 62 | typings/ 63 | 64 | # Optional npm cache directory 65 | .npm 66 | 67 | # Optional eslint cache 68 | .eslintcache 69 | 70 | # Optional REPL history 71 | .node_repl_history 72 | 73 | # Output of 'npm pack' 74 | *.tgz 75 | 76 | # Yarn Integrity file 77 | .yarn-integrity 78 | 79 | # dotenv environment variables file 80 | .env 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | 85 | # next.js build output 86 | .next 87 | 88 | # nuxt.js build output 89 | .nuxt 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless 96 | 97 | ### OSX ### 98 | # General 99 | .DS_Store 100 | .AppleDouble 101 | .LSOverride 102 | 103 | # Icon must end with two \r 104 | Icon 105 | 106 | # Thumbnails 107 | ._* 108 | 109 | # Files that might appear in the root of a volume 110 | .DocumentRevisions-V100 111 | .fseventsd 112 | .Spotlight-V100 113 | .TemporaryItems 114 | .Trashes 115 | .VolumeIcon.icns 116 | .com.apple.timemachine.donotpresent 117 | 118 | # Directories potentially created on remote AFP share 119 | .AppleDB 120 | .AppleDesktop 121 | Network Trash Folder 122 | Temporary Items 123 | .apdisk 124 | 125 | 126 | # End of https://www.gitignore.io/api/osx,node,linux 127 | 128 | # Coveralls key 129 | .coveralls.yml 130 | 131 | # Project custom ignored file 132 | dist 133 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "esbenp.prettier-vscode", 5 | "gruntfuggly.todo-tree" 6 | ] 7 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } 4 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | # API 2 | ## Classes 3 | 4 |
5 |
BufferStream
6 |

Buffer the stream content and bring it into the provided callback

7 |
8 |
9 | 10 | ## Functions 11 | 12 |
13 |
bufferStream(bufferCallback, options)
14 |

Utility function if you prefer a functional way of using this lib

15 |
16 |
bufferObjects(bufferCallback, options)
17 |

Utility function to buffer objet mode streams

18 |
19 |
20 | 21 | 22 | 23 | ## BufferStream 24 | Buffer the stream content and bring it into the provided callback 25 | 26 | **Kind**: global class 27 | 28 | 29 | ### new BufferStream(bufferCallback, options) 30 | 31 | | Param | Type | Description | 32 | | --- | --- | --- | 33 | | bufferCallback | function | A function to handle the buffered content. | 34 | | options | Object | inherits of Stream.Duplex, the options are passed to the parent constructor so you can use it's options too. | 35 | | options.objectMode | boolean | Use if piped in streams are in object mode. In this case, an array of the buffered will be transmitted to the callback function. | 36 | 37 | 38 | 39 | ## bufferStream(bufferCallback, options) ⇒ 40 | Utility function if you prefer a functional way of using this lib 41 | 42 | **Kind**: global function 43 | **Returns**: Stream 44 | 45 | | Param | 46 | | --- | 47 | | bufferCallback | 48 | | options | 49 | 50 | 51 | 52 | ## bufferObjects(bufferCallback, options) ⇒ 53 | Utility function to buffer objet mode streams 54 | 55 | **Kind**: global function 56 | **Returns**: Stream 57 | 58 | | Param | 59 | | --- | 60 | | bufferCallback | 61 | | options | 62 | 63 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [4.0.0](https://github.com/nfroidure/BufferStreams/compare/v3.0.0...v4.0.0) (2024-07-17) 2 | 3 | 4 | ### Code Refactoring 5 | 6 | * **api:** typescript rewrite ([9bdffa4](https://github.com/nfroidure/BufferStreams/commit/9bdffa413890add00130eb0be6ea18a735614434)), closes [#5](https://github.com/nfroidure/BufferStreams/issues/5) 7 | 8 | 9 | ### BREAKING CHANGES 10 | 11 | * **api:** Require Node20+ 12 | 13 | 14 | 15 | # [3.0.0](https://github.com/nfroidure/BufferStreams/compare/v2.0.1...v3.0.0) (2019-11-15) 16 | 17 | 18 | 19 | 20 | ## [2.0.1](https://github.com/nfroidure/BufferStreams/compare/v2.0.0...v2.0.1) (2018-04-23) 21 | 22 | 23 | ### Bug Fixes 24 | 25 | * Remove debug dependency ([2719573](https://github.com/nfroidure/BufferStreams/commit/2719573)) 26 | 27 | 28 | 29 | 30 | # [2.0.0](https://github.com/nfroidure/BufferStreams/compare/v1.1.1...v2.0.0) (2017-12-06) 31 | 32 | 33 | ### build 34 | 35 | * **metapak-nfroidure:** Add metapak-nfroidure ([ed104f6](https://github.com/nfroidure/BufferStreams/commit/ed104f6)) 36 | 37 | 38 | ### BREAKING CHANGES 39 | 40 | * **metapak-nfroidure:** Removing support for versions minors to the last LTS 41 | 42 | 43 | 44 | ## Change Log 45 | 46 | ### upcoming (2017/03/05 07:36 +00:00) 47 | - [f1bea6b](https://github.com/nfroidure/bufferstreams/commit/f1bea6bc9ad1c013457b9b9137f6dae70e9cab6b) Add Node 4-6-7 (@nfroidure) 48 | 49 | ### v1.1.1 (2016/06/28 11:20 +00:00) 50 | - [acd1875](https://github.com/nfroidure/bufferstreams/commit/acd18756a3066c72b4cff8aa79aa44a3dcb4f85f) 1.1.1 (@shinnn) 51 | - [4fc64b9](https://github.com/nfroidure/bufferstreams/commit/4fc64b9be16693e59920b976d4822adf52ec6f3d) Use wider badges to fit modern Github design (@shinnn) 52 | - [2b28391](https://github.com/nfroidure/bufferstreams/commit/2b2839191eec654af9361fc654d430f69bc6d622) Fix invalid license statement in package.json (@shinnn) 53 | - [e330151](https://github.com/nfroidure/bufferstreams/commit/e330151fd994b90d4383e2e189f18f628c3c1157) Fix file mode (@shinnn) 54 | - [#4](https://github.com/nfroidure/bufferstreams/pull/4) Use files field instead of .npmignore (@shinnn) 55 | - [d7435e5](https://github.com/nfroidure/bufferstreams/commit/d7435e5729b4684492e746d015bbfee39ac564dc) Use files field instead of .npmignore (@shinnn) 56 | 57 | ### v1.1.0 (2015/08/04 09:48 +00:00) 58 | - [8b3f905](https://github.com/nfroidure/bufferstreams/commit/8b3f9058764b4ff8095df90eea55ea7683d3ee33) 1.1.0 (@nfroidure) 59 | - [e7a4854](https://github.com/nfroidure/bufferstreams/commit/e7a4854d032682593851e20c20ac0b63587594d7) Better doc (@nfroidure) 60 | - [0567b10](https://github.com/nfroidure/bufferstreams/commit/0567b10f2a8cc6ddb1eada72a9178533b1fa1dd2) Also build on Node 0.12 (@nfroidure) 61 | - [92cd9de](https://github.com/nfroidure/bufferstreams/commit/92cd9de1752cbb70d403c4fbbee0532e762bf3e1) Dependencies update (@nfroidure) 62 | - [5bc3649](https://github.com/nfroidure/bufferstreams/commit/5bc3649e1c8f58505cad3393cbf67c47581dac13) Better handling of errors (@nfroidure) 63 | - [1ca6222](https://github.com/nfroidure/bufferstreams/commit/1ca6222b488072004305156c94a388c100df6fc7) Code cleanup and backward compatibility (@nfroidure) 64 | - [999b805](https://github.com/nfroidure/bufferstreams/commit/999b805be2007b5a1100f9cdbf0f911aa667afab) Using Duplex stream instead of Transform one (@nfroidure) 65 | - [77051e3](https://github.com/nfroidure/bufferstreams/commit/77051e330e82931acf3fb4c4b0b4f24aa4ae13de) Add tests for callback error emitting (@nfroidure) 66 | - [6475b82](https://github.com/nfroidure/bufferstreams/commit/6475b820cbc5a80f4e98bea86cdda3acc6ec7743) Adding linter and suppress warnings (@nfroidure) 67 | - [8d24a90](https://github.com/nfroidure/bufferstreams/commit/8d24a90bf5b91c1185f5c5f00cc32a4b65fc9fc5) Improving the README file (@nfroidure) 68 | - [d5ab44e](https://github.com/nfroidure/bufferstreams/commit/d5ab44e5ddeaddd73744158d046eb71b2fb29fb6) Add stats (@nfroidure) 69 | 70 | ### v1.0.2 (2015/06/21 07:11 +00:00) 71 | - [8aaf7a5](https://github.com/nfroidure/bufferstreams/commit/8aaf7a576177d3c2c1e626d8bfba091d47d7cea3) 1.0.2 (@nfroidure) 72 | - [fe52a06](https://github.com/nfroidure/bufferstreams/commit/fe52a06f252b161676f722624e74e9290b845320) Fix trinity script (@nfroidure) 73 | - [d512161](https://github.com/nfroidure/bufferstreams/commit/d5121614d055a63fca0072ebfc360fc17f8e04aa) Dependencies update (@nfroidure) 74 | - [48687ed](https://github.com/nfroidure/bufferstreams/commit/48687ed86eea5262693bb9b11097003bc0985b19) Merge branch 'master' of github.com:nfroidure/bufferstreams (@nfroidure) 75 | - [#3](https://github.com/nfroidure/bufferstreams/pull/3) Use SVG-based badges (@shinnn) 76 | - [88eccc5](https://github.com/nfroidure/bufferstreams/commit/88eccc50275e9317cba820a72f69f611c69cf3ab) Use SVG-based badges (@shinnn) 77 | - [a2875b3](https://github.com/nfroidure/bufferstreams/commit/a2875b323ec90b0d4d7afb60186a07c9f60f9350) Simplify test scripts (@shinnn) 78 | 79 | ### v1.0.1 (2015/02/09 17:57 +00:00) 80 | - [7d4c975](https://github.com/nfroidure/bufferstreams/commit/7d4c975accd17ea382845d93e11761ad8c364534) 1.0.1 (@nfroidure) 81 | - [6027553](https://github.com/nfroidure/bufferstreams/commit/602755373fd77d9ca34248b2ba106bcd57e49dce) Update deps (@nfroidure) 82 | 83 | ### v1.0.0 (2015/02/09 17:49 +00:00) 84 | - [438812d](https://github.com/nfroidure/bufferstreams/commit/438812dc9e6ecfbc7bd184503a341b0627aa9bf6) 1.0.0 (@nfroidure) 85 | - [01859b0](https://github.com/nfroidure/bufferstreams/commit/01859b0b32a8bb630d97aec3e01dac9148fd7b17) Documenting objectMode (@nfroidure) 86 | - [d3cac8d](https://github.com/nfroidure/bufferstreams/commit/d3cac8dab9a8d6f8275e82eedfafe929b156c258) Fixing tests (@nfroidure) 87 | - [d7ae39a](https://github.com/nfroidure/bufferstreams/commit/d7ae39a4bab7f77a93cde5993f8da703e21db0f0) Adding code climate metrics badge (@nfroidure) 88 | - [677f394](https://github.com/nfroidure/bufferstreams/commit/677f394d68a49afd6a24865a111c061985f8739f) Allowing objectMode, adding a better test lib and 100% coveraging (@nfroidure) 89 | - [1bf9b40](https://github.com/nfroidure/bufferstreams/commit/1bf9b404b47c801387867b9a3614041c5285cfe2) New version 0.0.2 (@nfroidure) 90 | - [17723e2](https://github.com/nfroidure/bufferstreams/commit/17723e212599d3a6dd582980b98331c0651a93ea) Added readable-stream closes#1 (@nfroidure) 91 | - [51545c3](https://github.com/nfroidure/bufferstreams/commit/51545c3e26b26315c17b17bb9a09986b39a538bf) Accept forgiving new (@nfroidure) 92 | - [8d87f57](https://github.com/nfroidure/bufferstreams/commit/8d87f576047d65b2a86680df4d4a6469202d96e1) Dependencies update (@nfroidure) 93 | - [3a1408f](https://github.com/nfroidure/bufferstreams/commit/3a1408fd7886a0dd5f1edf357d4c2373521ae032) Added deps (@nfroidure) 94 | - [38f0690](https://github.com/nfroidure/bufferstreams/commit/38f06902efe745a9cbf648fe6634a2c78c18a544) Added coverage tests (@nfroidure) 95 | - [2a631d6](https://github.com/nfroidure/bufferstreams/commit/2a631d66a41547e32f79dcba2ffe164d3f890bf9) Typo fix (@nfroidure) 96 | - [7ce57c9](https://github.com/nfroidure/bufferstreams/commit/7ce57c96aa95a24038442026b318f98ee5e9318e) Fixing npm badge (@nfroidure) 97 | - [c6d7b48](https://github.com/nfroidure/bufferstreams/commit/c6d7b486bc537b57997dd329966d53a216f30bff) Adding npm badge (@nfroidure) 98 | - [bfbca40](https://github.com/nfroidure/bufferstreams/commit/bfbca40112d3341989961a7ddbed838d2620b8e2) Adding instructions, fixing naming (@nfroidure) 99 | - [1a531c7](https://github.com/nfroidure/bufferstreams/commit/1a531c78b79642c76bcdd09aa0262342833e6203) Readme updated (@nfroidure) 100 | - [b0e1be8](https://github.com/nfroidure/bufferstreams/commit/b0e1be875aa9583ed44b2f1e9c74d050ef777ebb) First commit (@nfroidure) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright © 2017 Nicolas Froidure 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the “Software”), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [//]: # ( ) 2 | [//]: # (This file is automatically generated by a `metapak`) 3 | [//]: # (module. Do not change it except between the) 4 | [//]: # (`content:start/end` flags, your changes would) 5 | [//]: # (be overridden.) 6 | [//]: # ( ) 7 | # bufferstreams 8 | > Abstract streams to deal with the whole buffered contents. 9 | 10 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/nfroidure/bufferstreams/blob/main/LICENSE) 11 | [![Coverage Status](https://coveralls.io/repos/github/nfroidure/bufferstreams/badge.svg?branch=main)](https://coveralls.io/github/nfroidure/bufferstreams?branch=main) 12 | 13 | 14 | [//]: # (::contents:start) 15 | 16 | `bufferstreams` abstracts streams to allow you to deal with their whole content 17 | in a single buffer when it becomes necessary (by example: a legacy library that 18 | do not support streams). 19 | 20 | It is not a good practice (dealing with the whole stream content means you need 21 | to keep the whole stream content in memory which is probably not what you intent 22 | by using streams), just some glue. Using `bufferstreams` means: 23 | 24 | - there is no library dealing with streams for your needs 25 | - you filled an issue to the wrapped library to support streams 26 | 27 | `bufferstreams` can also be used to control the whole stream content in a single 28 | point of a streaming pipeline for testing purposes. 29 | 30 | ## Usage 31 | 32 | Install the [npm module](https://npmjs.org/package/bufferstreams): 33 | 34 | ```sh 35 | npm install bufferstreams --save 36 | ``` 37 | 38 | Then, in your scripts: 39 | 40 | ```js 41 | import fs from 'fs'; 42 | import { BufferStream } from 'bufferstreams'; 43 | 44 | fs.createReadStream('input.txt') 45 | .pipe( 46 | new BufferStream((err, buf, cb) => { 47 | // err will be filled with an error if the piped in stream emits one. 48 | if (err) { 49 | throw err; 50 | } 51 | 52 | // buf will contain the whole piped in stream contents 53 | buf = Buffer.from(buf.toString('utf-8').replace('foo', 'bar')); 54 | 55 | // cb is a callback to pass the result back to the piped out stream 56 | // first argument is an error that will be emitted if any 57 | // the second argument is the modified buffer 58 | cb(null, buf); 59 | }), 60 | ) 61 | .pipe(fs.createWriteStream('output.txt')); 62 | ``` 63 | 64 | Note that you can use `bufferstreams` with the objectMode option. In this case, 65 | the given buffer will be an array containing the streamed objects: 66 | 67 | ```js 68 | new BufferStreams(myCallback, { objectMode: true }); 69 | ``` 70 | 71 | `bufferstreams` exposes a utility function for functional programming: 72 | 73 | ```js 74 | import { streamBuffer } from 'bufferstreams'; 75 | 76 | process.stdin.pipe(streamBuffer(myCallback)).pipe(process.stdout); 77 | ``` 78 | 79 | Finally `bufferstreams` exposes another function for objects mode buffering: 80 | 81 | ```js 82 | import { bufferObjects } from 'bufferstreams'; 83 | 84 | process.stdin.pipe(bufferObjects(myCallback)).pipe(process.stdout); 85 | ``` 86 | 87 | ## Contributing 88 | 89 | Feel free to contribute with your code if you agree with publishing it under the 90 | MIT license. 91 | 92 | [//]: # (::contents:end) 93 | 94 | # API 95 | ## Classes 96 | 97 |
98 |
BufferStream
99 |

Buffer the stream content and bring it into the provided callback

100 |
101 |
102 | 103 | ## Functions 104 | 105 |
106 |
bufferStream(bufferCallback, options)
107 |

Utility function if you prefer a functional way of using this lib

108 |
109 |
bufferObjects(bufferCallback, options)
110 |

Utility function to buffer objet mode streams

111 |
112 |
113 | 114 | 115 | 116 | ## BufferStream 117 | Buffer the stream content and bring it into the provided callback 118 | 119 | **Kind**: global class 120 | 121 | 122 | ### new BufferStream(bufferCallback, options) 123 | 124 | | Param | Type | Description | 125 | | --- | --- | --- | 126 | | bufferCallback | function | A function to handle the buffered content. | 127 | | options | Object | inherits of Stream.Duplex, the options are passed to the parent constructor so you can use it's options too. | 128 | | options.objectMode | boolean | Use if piped in streams are in object mode. In this case, an array of the buffered will be transmitted to the callback function. | 129 | 130 | 131 | 132 | ## bufferStream(bufferCallback, options) ⇒ 133 | Utility function if you prefer a functional way of using this lib 134 | 135 | **Kind**: global function 136 | **Returns**: Stream 137 | 138 | | Param | 139 | | --- | 140 | | bufferCallback | 141 | | options | 142 | 143 | 144 | 145 | ## bufferObjects(bufferCallback, options) ⇒ 146 | Utility function to buffer objet mode streams 147 | 148 | **Kind**: global function 149 | **Returns**: Stream 150 | 151 | | Param | 152 | | --- | 153 | | bufferCallback | 154 | | options | 155 | 156 | 157 | # Authors 158 | - [Nicolas Froidure](http://insertafter.com/en/index.html) 159 | 160 | # License 161 | [MIT](https://github.com/nfroidure/bufferstreams/blob/main/LICENSE) 162 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // This file is automatically generated by a `metapak` 3 | // module. Do not change it elsewhere, changes would 4 | // be overridden. 5 | 6 | import eslint from '@eslint/js'; 7 | import tseslint from 'typescript-eslint'; 8 | import eslintConfigPrettier from 'eslint-config-prettier'; 9 | import eslintPluginJest from 'eslint-plugin-jest'; 10 | 11 | export default tseslint.config( 12 | eslint.configs.recommended, 13 | ...tseslint.configs.recommended, 14 | { 15 | files: ['*.test.ts'], 16 | ...eslintPluginJest.configs['flat/recommended'], 17 | }, 18 | eslintConfigPrettier, 19 | { 20 | name: 'Project config', 21 | languageOptions: { 22 | ecmaVersion: 2018, 23 | sourceType: 'module', 24 | }, 25 | ignores: ['*.d.ts'], 26 | }, 27 | ); 28 | 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "metapak": { 3 | "configs": [ 4 | "main", 5 | "readme", 6 | "eslint", 7 | "tsesm", 8 | "jest", 9 | "jsdocs", 10 | "codeclimate", 11 | "coveralls", 12 | "ghactions" 13 | ], 14 | "data": { 15 | "files": "'src/**/*.ts'", 16 | "testsFiles": "'src/**/*.tests.ts'", 17 | "distFiles": "'dist/**/*.js'", 18 | "ignore": [ 19 | "dist" 20 | ], 21 | "bundleFiles": [ 22 | "dist", 23 | "src" 24 | ] 25 | } 26 | }, 27 | "name": "bufferstreams", 28 | "version": "4.0.0", 29 | "description": "Abstract streams to deal with the whole buffered contents.", 30 | "homepage": "https://github.com/nfroidure/BufferStreams", 31 | "main": "dist/index.js", 32 | "files": [ 33 | "dist", 34 | "src", 35 | "LICENSE", 36 | "README.md", 37 | "CHANGELOG.md" 38 | ], 39 | "scripts": { 40 | "build": "rimraf 'dist' && tsc --outDir dist", 41 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md", 42 | "cli": "env NODE_ENV=${NODE_ENV:-cli}", 43 | "cover": "npm run jest -- --coverage", 44 | "cz": "env NODE_ENV=${NODE_ENV:-cli} git cz", 45 | "doc": "echo \"# API\" > API.md; jsdoc2md 'dist/**/*.js' >> API.md && git add API.md", 46 | "format": "npm run prettier", 47 | "jest": "NODE_OPTIONS=--experimental-vm-modules NODE_ENV=test jest", 48 | "lint": "eslint 'src/**/*.ts'", 49 | "metapak": "metapak", 50 | "precz": "npm t && npm run lint && npm run build && npm run doc && npm run metapak -- -s", 51 | "prettier": "prettier --write 'src/**/*.ts'", 52 | "preversion": "npm t && npm run lint && npm run build && npm run doc && npm run metapak -- -s", 53 | "rebuild": "swc ./src -s -d dist -C jsc.target=es2022", 54 | "test": "npm run jest", 55 | "type-check": "tsc --pretty --noEmit", 56 | "version": "npm run changelog" 57 | }, 58 | "repository": { 59 | "type": "git", 60 | "url": "git+https://github.com/nfroidure/bufferstreams.git" 61 | }, 62 | "keywords": [ 63 | "buffer", 64 | "streaming", 65 | "stream", 66 | "async", 67 | "abstract" 68 | ], 69 | "dependencies": { 70 | "readable-stream": "^3.4.0", 71 | "yerror": "^8.0.0" 72 | }, 73 | "devDependencies": { 74 | "@eslint/js": "^9.7.0", 75 | "@swc/cli": "^0.4.0", 76 | "@swc/core": "^1.6.13", 77 | "@swc/helpers": "^0.5.12", 78 | "@swc/jest": "^0.2.36", 79 | "commitizen": "^4.3.0", 80 | "conventional-changelog-cli": "^5.0.0", 81 | "cz-conventional-changelog": "^3.3.0", 82 | "eslint": "^9.7.0", 83 | "eslint-config-prettier": "^9.1.0", 84 | "eslint-plugin-jest": "^28.6.0", 85 | "eslint-plugin-prettier": "^5.1.3", 86 | "jest": "^29.7.0", 87 | "jsdoc-to-markdown": "^8.0.0", 88 | "metapak": "^6.0.1", 89 | "metapak-nfroidure": "^18.2.0", 90 | "prettier": "^3.3.3", 91 | "rimraf": "^6.0.1", 92 | "streamtest": "^3.0.0", 93 | "typescript": "^5.5.3", 94 | "typescript-eslint": "^7.16.0" 95 | }, 96 | "author": { 97 | "name": "Nicolas Froidure", 98 | "email": "nicolas.froidure@insertafter.com", 99 | "url": "http://insertafter.com/en/index.html" 100 | }, 101 | "engines": { 102 | "node": ">=20.11.1" 103 | }, 104 | "license": "MIT", 105 | "bugs": { 106 | "url": "https://github.com/nfroidure/BufferStreams/issues" 107 | }, 108 | "config": { 109 | "commitizen": { 110 | "path": "./node_modules/cz-conventional-changelog" 111 | } 112 | }, 113 | "greenkeeper": { 114 | "ignore": [ 115 | "commitizen", 116 | "cz-conventional-changelog", 117 | "conventional-changelog-cli", 118 | "eslint", 119 | "prettier", 120 | "eslint-config-prettier", 121 | "eslint-plugin-prettier", 122 | "typescript-eslint", 123 | "typescript", 124 | "rimraf", 125 | "@swc/cli", 126 | "@swc/core", 127 | "@swc/helpers", 128 | "jest", 129 | "@swc/jest", 130 | "jsdoc-to-markdown" 131 | ] 132 | }, 133 | "prettier": { 134 | "semi": true, 135 | "printWidth": 80, 136 | "singleQuote": true, 137 | "trailingComma": "all", 138 | "proseWrap": "always" 139 | }, 140 | "contributors": [], 141 | "jest": { 142 | "coverageReporters": [ 143 | "lcov" 144 | ], 145 | "testPathIgnorePatterns": [ 146 | "/node_modules/" 147 | ], 148 | "roots": [ 149 | "/src" 150 | ], 151 | "transform": { 152 | "^.+\\.tsx?$": [ 153 | "@swc/jest", 154 | {} 155 | ] 156 | }, 157 | "testEnvironment": "node", 158 | "moduleNameMapper": { 159 | "(.+)\\.js": "$1" 160 | }, 161 | "extensionsToTreatAsEsm": [ 162 | ".ts" 163 | ], 164 | "prettierPath": null 165 | }, 166 | "overrides": { 167 | "eslint": "^9.7.0" 168 | }, 169 | "type": "module", 170 | "types": "dist/index.d.ts" 171 | } 172 | -------------------------------------------------------------------------------- /src/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from '@jest/globals'; 2 | import { YError } from 'yerror'; 3 | import StreamTest from 'streamtest'; 4 | import { BufferStream } from './index.js'; 5 | 6 | // Helpers 7 | function syncBufferPrefixer(headerText: string) { 8 | return new BufferStream( 9 | (err, buf, cb) => { 10 | expect(err).toBeNull(); 11 | if (null === buf) { 12 | cb(null, Buffer.from(headerText)); 13 | return; 14 | } 15 | cb(null, Buffer.concat([Buffer.from(headerText), buf])); 16 | }, 17 | { 18 | objectMode: false, 19 | }, 20 | ); 21 | } 22 | 23 | function syncObjectsPrefixer(prefixObject: T) { 24 | return new BufferStream( 25 | (err, objs, cb) => { 26 | expect(err).toBeNull(); 27 | if (null === objs) { 28 | cb(null, [prefixObject]); 29 | return; 30 | } 31 | cb(null, [prefixObject, ...objs]); 32 | }, 33 | { 34 | objectMode: true, 35 | }, 36 | ); 37 | } 38 | 39 | function asyncBufferPrefixer(headerText: string) { 40 | return new BufferStream( 41 | (err, buf, cb) => { 42 | expect(err).toBeNull(); 43 | if (null === buf) { 44 | setTimeout(() => { 45 | cb(null, Buffer.from(headerText)); 46 | }, 0); 47 | } else { 48 | setTimeout(() => { 49 | cb(null, Buffer.concat([Buffer.from(headerText), buf])); 50 | }, 0); 51 | } 52 | }, 53 | { 54 | objectMode: false, 55 | }, 56 | ); 57 | } 58 | 59 | describe('bufferstreams', () => { 60 | test('should fail when callback is not a function', () => { 61 | try { 62 | new BufferStream(undefined as unknown as (t: T) => Promise); 63 | throw new YError('E_UNEXPECTED_SUCCESS'); 64 | } catch (err) { 65 | expect((err as YError).code).toEqual('E_BAD_CALLBACK'); 66 | } 67 | }); 68 | 69 | describe('in buffer mode', () => { 70 | describe('synchonously', () => { 71 | test('should work with one pipe', async () => { 72 | const [stream, result] = StreamTest.toText(); 73 | 74 | StreamTest.fromChunks([Buffer.from('te'), Buffer.from('st')]) 75 | .pipe(syncBufferPrefixer('plop')) 76 | .pipe(stream); 77 | 78 | expect(await result).toEqual('ploptest'); 79 | }); 80 | 81 | test('should work when returning a null buffer', async () => { 82 | const [stream, result] = StreamTest.toText(); 83 | 84 | StreamTest.fromChunks([Buffer.from('te'), Buffer.from('st')]) 85 | .pipe( 86 | new BufferStream((err, buf, cb) => { 87 | if (err) { 88 | cb(err); 89 | return; 90 | } 91 | cb(null, null); 92 | }), 93 | ) 94 | .pipe(stream); 95 | 96 | expect(await result).toEqual(''); 97 | }); 98 | 99 | test('should work with an async handler', async () => { 100 | const [stream, result] = StreamTest.toText(); 101 | 102 | StreamTest.fromChunks([Buffer.from('te'), Buffer.from('st')]) 103 | .pipe( 104 | new BufferStream(async (buf) => { 105 | return buf; 106 | }), 107 | ) 108 | .pipe(stream); 109 | 110 | expect(await result).toEqual('test'); 111 | }); 112 | 113 | test('should work with multiple pipes', async () => { 114 | const [stream, result] = StreamTest.toText(); 115 | 116 | StreamTest.fromChunks([Buffer.from('te'), Buffer.from('st')]) 117 | .pipe(syncBufferPrefixer('plop')) 118 | .pipe(syncBufferPrefixer('plip')) 119 | .pipe(syncBufferPrefixer('plap')) 120 | .pipe(stream); 121 | 122 | expect(await result).toEqual('plapplipploptest'); 123 | }); 124 | }); 125 | 126 | describe('asynchonously', () => { 127 | test('should work with one pipe', async () => { 128 | const [stream, result] = StreamTest.toText(); 129 | 130 | StreamTest.fromChunks([Buffer.from('te'), Buffer.from('st')]) 131 | .pipe(asyncBufferPrefixer('plop')) 132 | .pipe(stream); 133 | 134 | expect(await result).toEqual('ploptest'); 135 | }); 136 | 137 | test('should work when returning a null buffer', async () => { 138 | const [stream, result] = StreamTest.toText(); 139 | 140 | StreamTest.fromChunks([Buffer.from('te'), Buffer.from('st')]) 141 | .pipe( 142 | new BufferStream((err, _buf, cb) => { 143 | if (err) { 144 | cb(err); 145 | return; 146 | } 147 | cb(null, null); 148 | }), 149 | ) 150 | .pipe(stream); 151 | 152 | expect(await result).toEqual(''); 153 | }); 154 | 155 | test('should work with multiple pipes', async () => { 156 | const [stream, result] = StreamTest.toText(); 157 | 158 | StreamTest.fromChunks([Buffer.from('te'), Buffer.from('st')]) 159 | .pipe(asyncBufferPrefixer('plop')) 160 | .pipe(asyncBufferPrefixer('plip')) 161 | .pipe(asyncBufferPrefixer('plap')) 162 | .pipe(stream); 163 | 164 | expect(await result).toEqual('plapplipploptest'); 165 | }); 166 | 167 | test('should report stream errors', async () => { 168 | const [stream, result] = StreamTest.toText(); 169 | const bufferStream = new BufferStream( 170 | (err, _objs, cb) => { 171 | expect((err as YError).code).toEqual('E_ERROR'); 172 | cb(null, []); 173 | }, 174 | { 175 | objectMode: true, 176 | }, 177 | ); 178 | 179 | StreamTest.fromErroredChunks(new YError('E_ERROR'), [ 180 | Buffer.from('ou'), 181 | Buffer.from('de'), 182 | Buffer.from('la'), 183 | Buffer.from('li'), 184 | ]) 185 | .on('error', (err) => { 186 | bufferStream.emit('error', err); 187 | }) 188 | .pipe(bufferStream) 189 | .pipe(stream); 190 | 191 | expect(await result).toEqual(''); 192 | }); 193 | 194 | test('should emit callback errors', async () => { 195 | const [stream, result] = StreamTest.toText(); 196 | let caughtError: YError = new YError('E_UNEXPECTED_SUCCESS'); 197 | 198 | StreamTest.fromChunks([ 199 | Buffer.from('ou'), 200 | Buffer.from('de'), 201 | Buffer.from('la'), 202 | Buffer.from('li'), 203 | ]) 204 | .pipe( 205 | new BufferStream((err, _objs, cb) => { 206 | if (err) { 207 | cb(err); 208 | return; 209 | } 210 | cb(new YError('E_ERROR'), Buffer.from('')); 211 | }), 212 | ) 213 | .on('error', (err) => { 214 | caughtError = err as YError; 215 | }) 216 | .pipe(stream); 217 | 218 | expect(await result).toEqual(''); 219 | expect(caughtError.code).toEqual('E_ERROR'); 220 | }); 221 | }); 222 | }); 223 | 224 | describe('in object mode', () => { 225 | const object1 = { txt: 'te' }; 226 | const object2 = { txt: 'st' }; 227 | const object3 = { txt: 'e' }; 228 | const object4 = { txt: 'd' }; 229 | const object5 = { txt: 'u' }; 230 | const object6 = { txt: 'ni' }; 231 | const object7 = { txt: 't' }; 232 | 233 | describe('synchonously', () => { 234 | test('should work with one pipe', async () => { 235 | const [stream, result] = StreamTest.toObjects(); 236 | 237 | StreamTest.fromObjects([object1, object2]) 238 | .pipe(syncObjectsPrefixer(object4)) 239 | .pipe(stream); 240 | expect(await result).toEqual([object4, object1, object2]); 241 | }); 242 | 243 | test('should work when returning an empty array', async () => { 244 | const [stream, result] = StreamTest.toObjects(); 245 | 246 | StreamTest.fromObjects([object1, object2]) 247 | .pipe( 248 | new BufferStream( 249 | (err, _objs, cb) => { 250 | if (err) { 251 | cb(err); 252 | return; 253 | } 254 | cb(null, []); 255 | }, 256 | { 257 | objectMode: true, 258 | }, 259 | ), 260 | ) 261 | .pipe(stream); 262 | expect((await result).length).toEqual(0); 263 | }); 264 | 265 | test('should work with multiple pipes', async () => { 266 | const [stream, result] = StreamTest.toObjects(); 267 | 268 | StreamTest.fromObjects([object1, object2]) 269 | .pipe(syncObjectsPrefixer(object4)) 270 | .pipe(syncObjectsPrefixer(object5)) 271 | .pipe(syncObjectsPrefixer(object6)) 272 | .pipe(stream); 273 | expect(await result).toEqual([ 274 | object6, 275 | object5, 276 | object4, 277 | object1, 278 | object2, 279 | ]); 280 | }); 281 | }); 282 | 283 | describe('asynchonously', () => { 284 | test('should work with one pipe', async () => { 285 | const [stream, result] = StreamTest.toObjects(); 286 | 287 | StreamTest.fromObjects([object1, object2]) 288 | .pipe(syncObjectsPrefixer(object4)) 289 | .pipe(stream); 290 | expect(await result).toEqual([object4, object1, object2]); 291 | }); 292 | 293 | test('should work when returning an empty array', async () => { 294 | const [stream, result] = StreamTest.toObjects(); 295 | 296 | StreamTest.fromObjects([object1, object2]) 297 | .pipe( 298 | new BufferStream( 299 | (err, _objs, cb) => { 300 | if (err) { 301 | cb(err); 302 | return; 303 | } 304 | cb(null, []); 305 | }, 306 | { 307 | objectMode: true, 308 | }, 309 | ), 310 | ) 311 | .pipe(stream); 312 | expect((await result).length).toEqual(0); 313 | }); 314 | 315 | test('should work when returning legacy null', async () => { 316 | const [stream, result] = StreamTest.toObjects(); 317 | 318 | StreamTest.fromObjects([object1, object2]) 319 | .pipe( 320 | new BufferStream( 321 | (err, _objs, cb) => { 322 | if (err) { 323 | cb(err); 324 | return; 325 | } 326 | cb(null, null); 327 | }, 328 | { 329 | objectMode: true, 330 | }, 331 | ), 332 | ) 333 | .pipe(stream); 334 | expect((await result).length).toEqual(0); 335 | }); 336 | 337 | test('should work with multiple pipes', async () => { 338 | const [stream, result] = StreamTest.toObjects(); 339 | 340 | StreamTest.fromObjects([object1, object2]) 341 | .pipe(syncObjectsPrefixer(object4)) 342 | .pipe(syncObjectsPrefixer(object5)) 343 | .pipe(syncObjectsPrefixer(object6)) 344 | .pipe(stream); 345 | expect(await result).toEqual([ 346 | object6, 347 | object5, 348 | object4, 349 | object1, 350 | object2, 351 | ]); 352 | }); 353 | 354 | test('should report stream errors', async () => { 355 | const [stream, result] = StreamTest.toObjects(); 356 | 357 | const bufferStream = new BufferStream( 358 | (err, _objs, cb) => { 359 | expect((err as YError).code).toEqual('E_ERROR'); 360 | cb(null, []); 361 | }, 362 | { 363 | objectMode: true, 364 | }, 365 | ); 366 | 367 | StreamTest.fromErroredObjects(new YError('E_ERROR'), [ 368 | object1, 369 | object2, 370 | object3, 371 | object4, 372 | object5, 373 | object6, 374 | object7, 375 | ]) 376 | .on('error', (err) => { 377 | bufferStream.emit('error', err); 378 | }) 379 | .pipe(bufferStream) 380 | .pipe(stream); 381 | expect(await result).toEqual([]); 382 | }); 383 | 384 | test('should emit callback errors', async () => { 385 | const [stream, result] = StreamTest.toObjects(); 386 | let caughtError: YError = new YError('E_UNEXPECTED_SUCCESS'); 387 | 388 | StreamTest.fromObjects([ 389 | object1, 390 | object2, 391 | object3, 392 | object4, 393 | object5, 394 | object6, 395 | object7, 396 | ]) 397 | .pipe( 398 | new BufferStream( 399 | (err, _objs, cb) => { 400 | if (err) { 401 | cb(err, []); 402 | return; 403 | } 404 | cb(new YError('E_ERROR'), []); 405 | }, 406 | { 407 | objectMode: true, 408 | }, 409 | ), 410 | ) 411 | .on('error', (err) => { 412 | caughtError = err as YError; 413 | }) 414 | .pipe(stream); 415 | 416 | expect(await result).toEqual([]); 417 | expect(caughtError.code).toEqual('E_ERROR'); 418 | }); 419 | }); 420 | }); 421 | }); 422 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Duplex, type Writable } from 'stream'; 2 | import { YError } from 'yerror'; 3 | 4 | export type BufferStreamOptions = { 5 | objectMode: boolean; 6 | }; 7 | export type BufferStreamItem< 8 | O extends Partial, 9 | T, 10 | > = O extends { objectMode: true } ? T : Buffer; 11 | export type BufferStreamPayload< 12 | O extends Partial, 13 | T, 14 | > = O extends { objectMode: true } ? T[] : Buffer; 15 | export type BufferStreamHandler, T> = ( 16 | payload: BufferStreamPayload, 17 | ) => Promise>; 18 | export type BufferStreamCallback, T> = ( 19 | err: Error | null, 20 | payload: BufferStreamPayload, 21 | cb: (err: Error | null, payload?: null | BufferStreamPayload) => void, 22 | ) => void; 23 | 24 | const DEFAULT_BUFFER_STREAM_OPTIONS = { 25 | objectMode: false, 26 | }; 27 | 28 | /** 29 | * Buffer the stream content and bring it into the provided callback 30 | */ 31 | class BufferStream> extends Duplex { 32 | private _options: BufferStreamOptions = DEFAULT_BUFFER_STREAM_OPTIONS; 33 | private _bufferCallback: BufferStreamCallback; 34 | private _finished: boolean = false; 35 | private _buffer: BufferStreamItem[] = []; 36 | 37 | /** 38 | * @param bufferCallback {Function} A function to handle the buffered content. 39 | * @param options {Object} inherits of Stream.Duplex, the options are passed to the parent constructor so you can use it's options too. 40 | * @param options.objectMode {boolean} Use if piped in streams are in object mode. In this case, an array of the buffered will be transmitted to the callback function. 41 | */ 42 | constructor( 43 | bufferCallback: BufferStreamCallback | BufferStreamHandler, 44 | options?: O, 45 | ) { 46 | super(options); 47 | 48 | if (!(bufferCallback instanceof Function)) { 49 | throw new YError('E_BAD_CALLBACK'); 50 | } 51 | 52 | this._options = { 53 | ...DEFAULT_BUFFER_STREAM_OPTIONS, 54 | ...options, 55 | }; 56 | this._bufferCallback = 57 | bufferCallback.length === 1 58 | ? (((err, payload, cb) => { 59 | (bufferCallback as BufferStreamHandler)(payload) 60 | .then((result) => { 61 | cb(err, result); 62 | }) 63 | .catch((err) => { 64 | cb(err); 65 | }); 66 | }) as BufferStreamCallback) 67 | : (bufferCallback as BufferStreamCallback); 68 | 69 | this.once('finish', this._bufferStreamCallbackWrapper); 70 | this.on('error', this._bufferStreamError); 71 | } 72 | 73 | _write( 74 | chunk: BufferStreamItem, 75 | encoding: Parameters[1], 76 | done: () => void, 77 | ) { 78 | this._buffer.push(chunk); 79 | done(); 80 | } 81 | 82 | _read() { 83 | if (this._finished) { 84 | while (this._buffer.length) { 85 | if (!this.push(this._buffer.shift())) { 86 | break; 87 | } 88 | } 89 | if (0 === this._buffer.length) { 90 | this.push(null); 91 | } 92 | } 93 | } 94 | 95 | _bufferStreamCallbackWrapper(err: Error) { 96 | const buffer = ( 97 | this._options.objectMode 98 | ? (this._buffer as T[]) 99 | : Buffer.concat(this._buffer as Buffer[]) 100 | ) as O extends { 101 | objectMode: true; 102 | } 103 | ? T[] 104 | : Buffer; 105 | 106 | err = err || null; 107 | 108 | this._bufferCallback(err, buffer, (err2, buf) => { 109 | setImmediate(() => { 110 | this.removeListener('error', this._bufferStreamError); 111 | if (err2) { 112 | this.emit('error', err2); 113 | } 114 | this._buffer = ( 115 | buf == null ? [] : buf instanceof Buffer ? [buf] : buf 116 | ) as BufferStreamItem[]; 117 | this._finished = true; 118 | this._read(); 119 | }); 120 | }); 121 | } 122 | 123 | _bufferStreamError(err: Error) { 124 | if (this._finished) { 125 | return; 126 | } 127 | this._bufferStreamCallbackWrapper(err); 128 | } 129 | } 130 | 131 | /** 132 | * Utility function if you prefer a functional way of using this lib 133 | * @param bufferCallback 134 | * @param options 135 | * @returns Stream 136 | */ 137 | export function bufferStream>( 138 | bufferCallback: BufferStreamCallback, 139 | options: O = DEFAULT_BUFFER_STREAM_OPTIONS as O, 140 | ) { 141 | return new BufferStream(bufferCallback, options); 142 | } 143 | 144 | /** 145 | * Utility function to buffer objet mode streams 146 | * @param bufferCallback 147 | * @param options 148 | * @returns Stream 149 | */ 150 | export function bufferObjects( 151 | bufferCallback: BufferStreamCallback<{ objectMode: true }, T>, 152 | options: Omit, 153 | ) { 154 | return new BufferStream(bufferCallback, { 155 | ...options, 156 | objectMode: true, 157 | }); 158 | } 159 | 160 | export { BufferStream }; 161 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "Node16", 4 | "moduleResolution": "Node16", 5 | "target": "es2022", 6 | "noImplicitAny": false, 7 | "removeComments": false, 8 | "preserveConstEnums": true, 9 | "allowSyntheticDefaultImports": true, 10 | "esModuleInterop": true, 11 | "strict": true, 12 | "declaration": true, 13 | "outDir": "dist", 14 | "sourceMap": true 15 | }, 16 | "include": [ 17 | "src/**/*.ts" 18 | ], 19 | "exclude": [ 20 | "node_modules" 21 | ] 22 | } --------------------------------------------------------------------------------