├── .codeclimate.yml ├── .editorconfig ├── .gitattributes ├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING ├── FUNDING.yml ├── ISSUE_TEMPLATE ├── PULL_REQUEST_TEMPLATE └── workflows │ └── test.yml ├── .gitignore ├── .npmignore ├── .readme └── contents.md ├── .travis.yml ├── .vscode ├── extensions.json └── settings.json ├── API.md ├── CHANGELOG.md ├── LICENCE ├── 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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.swp 3 | node_modules/ 4 | .git 5 | tests/results 6 | .coveralls.yml 7 | -------------------------------------------------------------------------------- /.readme/contents.md: -------------------------------------------------------------------------------- 1 | ## Usage 2 | Install the [npm module](https://npmjs.org/package/streamqueue): 3 | ```sh 4 | npm install streamqueue --save 5 | ``` 6 | Then, in your scripts: 7 | ```js 8 | var streamqueue = require('streamqueue'); 9 | 10 | var queue = streamqueue( 11 | Fs.createReadStream('input.txt'), 12 | Fs.createReadStream('input2.txt'), 13 | Fs.createReadStream('input3.txt') 14 | ).pipe(process.stdout); 15 | ``` 16 | StreamQueue also accept functions returning streams, the above can be written 17 | like this, doing system calls only when piping: 18 | ```js 19 | var streamqueue = require('streamqueue'); 20 | 21 | var queue = streamqueue( 22 | Fs.createReadStream.bind(null, 'input.txt'), 23 | Fs.createReadStream.bind(null, 'input2.txt'), 24 | Fs.createReadStream.bind(null, 'input3.txt') 25 | ).pipe(process.stdout); 26 | ``` 27 | 28 | Object-oriented traditionnal API offers more flexibility: 29 | ```js 30 | var StreamQueue = require('streamqueue'); 31 | 32 | var queue = new StreamQueue(); 33 | queue.queue( 34 | Fs.createReadStream('input.txt'), 35 | Fs.createReadStream('input2.txt'), 36 | Fs.createReadStream('input3.txt') 37 | ); 38 | queue.done(); 39 | 40 | queue.pipe(process.stdout); 41 | ``` 42 | You can also chain StreamQueue methods like that: 43 | ```js 44 | var StreamQueue = require('streamqueue'); 45 | 46 | new StreamQueue() 47 | .queue(Fs.createReadStream('input.txt')) 48 | .queue(Fs.createReadStream('input2.txt')) 49 | .queue(Fs.createReadStream('input3.txt')) 50 | .done() 51 | .pipe(process.stdout); 52 | ``` 53 | 54 | You can queue new streams at any moment until you call the done() method. So the 55 | created stream will not fire the end event until done() call. 56 | 57 | Note that stream queue is compatible with the Node 0.10+ streams. For older 58 | streams, stream queue will wrap them with 59 | [`Readable.wrap`](http://nodejs.org/api/stream.html#stream_readable_wrap_stream) 60 | before queueing. Please fix your dependencies or report issues to libraries 61 | using 0.8 streams since this extra code will finally be removed. 62 | 63 | ## API 64 | 65 | ### StreamQueue([options], [stream1, stream2, ... streamN]) 66 | 67 | #### options 68 | 69 | ##### options.objectMode 70 | Type: `Boolean` 71 | Default value: `false` 72 | 73 | Use if piped in streams are in object mode. In this case, the stream queue will 74 | also be in the object mode. 75 | 76 | ##### options.pauseFlowingStream 77 | Type: `Boolean` 78 | Default value: `true` 79 | 80 | If a stream is in flowing mode, then it will be paused before queueing. 81 | 82 | ##### options.resumeFlowingStream 83 | Type: `Boolean` 84 | Default value: `true` 85 | 86 | If a stream is in flowing mode, then it will be resumed before piping. 87 | 88 | ##### options.* 89 | 90 | StreamQueue inherits of Stream.PassThrough, the options are passed to the 91 | parent constructor so you can use it's options too. 92 | 93 | #### streamN 94 | Type: `Stream` 95 | 96 | Append streams given in argument to the queue and ends when the queue is empty. 97 | 98 | ### StreamQueue.queue(stream1, [stream2, ... streamN]) 99 | 100 | Append streams given in argument to the queue. 101 | 102 | ### StreamQueue.done([stream1, stream2, ... streamN]) 103 | 104 | Append streams given in argument to the queue and ends when the queue is empty. 105 | 106 | 107 | ### StreamQueue.obj([options], [stream1, stream2, ... streamN]) 108 | 109 | A shortcut for `StreamQueue({objectMode: true})`. 110 | 111 | ## Stats 112 | 113 | [![NPM](https://nodei.co/npm/streamqueue.png?downloads=true&stars=true)](https://nodei.co/npm/streamqueue/) 114 | [![NPM](https://nodei.co/npm-dl/streamqueue.png)](https://nodei.co/npm/streamqueue/) 115 | 116 | 117 | ## Contributing 118 | Feel free to pull your code if you agree with publishing it under the MIT license. 119 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by a `metapak` 2 | # module. Do not change it elsewhere, changes would 3 | # be overridden. 4 | 5 | language: node_js 6 | node_js: 7 | - 6 8 | - 6.9.5 9 | - 7 10 | -------------------------------------------------------------------------------- /.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 |
StreamQueue
6 |

Pipe queued streams sequentially

7 |
8 |
9 | 10 | ## Functions 11 | 12 |
13 |
queueObjectStreams(options, ...streams)
14 |

Create a new queue in object mode and pipe given streams and end if some

15 |
16 |
queueStreams(options, ...streams)
17 |

Create a new queue and pipe given streams and end if some

18 |
19 |
20 | 21 | 22 | 23 | ## StreamQueue 24 | Pipe queued streams sequentially 25 | 26 | **Kind**: global class 27 | 28 | * [StreamQueue](#StreamQueue) 29 | * [new StreamQueue(options, ...streams)](#new_StreamQueue_new) 30 | * [.queue(...streams)](#StreamQueue+queue) ⇒ 31 | * [.done(...streams)](#StreamQueue+done) ⇒ 32 | 33 | 34 | 35 | ### new StreamQueue(options, ...streams) 36 | Create a new queue and pipe given streams and end if some 37 | 38 | **Returns**: StreamQueue 39 | 40 | | Param | Type | Description | 41 | | --- | --- | --- | 42 | | options | Object | The queue options | 43 | | options.objectMode | boolean | Operate in object mode | 44 | | options.pauseFlowingStream | boolean | Pause given streams that are flowing | 45 | | options.resumeFlowingStream | boolean | Resume given streams that are flowing | 46 | | ...streams | Readable \| function | The stream or stream returning function to pipe in | 47 | 48 | 49 | 50 | ### streamQueue.queue(...streams) ⇒ 51 | Queue each stream given in argument 52 | 53 | **Kind**: instance method of [StreamQueue](#StreamQueue) 54 | **Returns**: StreamQueue 55 | 56 | | Param | Type | Description | 57 | | --- | --- | --- | 58 | | ...streams | Readable \| function | The stream or stream returning function to pipe in | 59 | 60 | 61 | 62 | ### streamQueue.done(...streams) ⇒ 63 | Queue each stream given in argument and end 64 | 65 | **Kind**: instance method of [StreamQueue](#StreamQueue) 66 | **Returns**: StreamQueue 67 | 68 | | Param | Type | Description | 69 | | --- | --- | --- | 70 | | ...streams | Readable \| function | The stream or stream returning function to pipe in | 71 | 72 | 73 | 74 | ## queueObjectStreams(options, ...streams) ⇒ 75 | Create a new queue in object mode and pipe given streams and end if some 76 | 77 | **Kind**: global function 78 | **Returns**: StreamQueue 79 | 80 | | Param | Type | Description | 81 | | --- | --- | --- | 82 | | options | Object | The queue options | 83 | | ...streams | Readable \| function | The stream or stream returning function to pipe in | 84 | 85 | 86 | 87 | ## queueStreams(options, ...streams) ⇒ 88 | Create a new queue and pipe given streams and end if some 89 | 90 | **Kind**: global function 91 | **Returns**: StreamQueue 92 | 93 | | Param | Type | Description | 94 | | --- | --- | --- | 95 | | options | Object | The queue options | 96 | | ...streams | Readable \| function | The stream or stream returning function to pipe in | 97 | 98 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [2.0.0](https://github.com/nfroidure/StreamQueue/compare/v1.1.2...v2.0.0) (2024-07-18) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * **docs:** fix markdown title ([984bdee](https://github.com/nfroidure/StreamQueue/commit/984bdee4581509d80df369f810ebbf5c73ec0a42)) 7 | 8 | 9 | ### Code Refactoring 10 | 11 | * **core:** complete refactoring ([a969877](https://github.com/nfroidure/StreamQueue/commit/a969877e4cf877a13d57b90c779342c47be7cbfc)) 12 | 13 | 14 | ### BREAKING CHANGES 15 | 16 | * **core:** Support only Node20+, use ESM, API surface changed. By updating assume you could 17 | have a lot of work to do. 18 | 19 | 20 | 21 | 22 | ## [1.1.2](https://github.com/nfroidure/StreamQueue/compare/v1.1.1...v1.1.2) (2017-12-03) 23 | 24 | 25 | ### build 26 | 27 | * **metapak-nfroidure:** Add metapak-nfroidure ([a8a9572](https://github.com/nfroidure/StreamQueue/commit/a8a9572)) 28 | 29 | 30 | ### BREAKING CHANGES 31 | 32 | * **metapak-nfroidure:** Remove support for Node version prior to the last LTS 33 | 34 | 35 | 36 | ### v1.1.1 (2015/08/27 11:33 +00:00) 37 | - [a772c39](https://github.com/nfroidure/streamqueue/commit/a772c3925e60fd2f26b7a2f8e0aa500793df62bc) 1.1.1 (@nfroidure) 38 | - [a2443fd](https://github.com/nfroidure/streamqueue/commit/a2443fdd28ccab84892f08f3d55a7c8b304e4fb3) Fixing the .obj() shortcut with no args fix #9 (@nfroidure) 39 | 40 | ### v1.1.0 (2015/06/09 06:19 +00:00) 41 | - [f8f8fc3](https://github.com/nfroidure/streamqueue/commit/f8f8fc35346ec7009021bfe51c89d03bc89dfb9b) 1.1.0 (@nfroidure) 42 | - [403331d](https://github.com/nfroidure/streamqueue/commit/403331d30d2a2be5599add4b94bd5fe84a73c512) Adding a .obj method (@nfroidure) 43 | 44 | ### v1.0.0 (2015/06/08 16:41 +00:00) 45 | - [1d1399e](https://github.com/nfroidure/streamqueue/commit/1d1399ee9bbd25d3d53d1eca74f40aea124e3d9e) 1.0.0 (@nfroidure) 46 | - [acf36b7](https://github.com/nfroidure/streamqueue/commit/acf36b7ed70192805bf1b2377c982918e7785f90) Updating dependencies (@nfroidure) 47 | - [7473d22](https://github.com/nfroidure/streamqueue/commit/7473d223388a26597c3bf48ca64b3b976bb96b95) Fixing hints (@nfroidure) 48 | - [cc0b79b](https://github.com/nfroidure/streamqueue/commit/cc0b79b5e4f70dc6dfe0ffa66d7992d68292c2d0) Fix travis build (@nfroidure) 49 | 50 | ### v0.1.3 (2015/02/14 14:40 +00:00) 51 | - [b54a812](https://github.com/nfroidure/streamqueue/commit/b54a8121c93bcb2283dd586b1f46f9f4e14aa5c2) 0.1.3 (@nfroidure) 52 | - [a0acd1b](https://github.com/nfroidure/streamqueue/commit/a0acd1b8112f53f7cc05a87c677e4e724ad7a906) 0.1.2 (@nfroidure) 53 | 54 | ### v0.1.2 (2015/02/14 14:36 +00:00) 55 | - [af60e80](https://github.com/nfroidure/streamqueue/commit/af60e80b2b59a5d82239221d5aba53d66668dd27) Removing uneccessary deps (@nfroidure) 56 | - [b43bcd0](https://github.com/nfroidure/streamqueue/commit/b43bcd032bd09ebc4154344858c5bc4d65f74788) Using streamtest for tests (@nfroidure) 57 | - [ca0e386](https://github.com/nfroidure/streamqueue/commit/ca0e386db013c373e2a8909a48dc0a8dace7d97f) Inherit of Readable instead of PassThrough (@nfroidure) 58 | - [47ec014](https://github.com/nfroidure/streamqueue/commit/47ec01474cd3819c08ccbbc08dd44f192c282ac4) Reverting setImmadiate for node 0.8 (@nfroidure) 59 | - [9b6be4a](https://github.com/nfroidure/streamqueue/commit/9b6be4a09da248db385ef12906ccdfd34240d35a) Isolation state prop in a single object (@nfroidure) 60 | - [962ec36](https://github.com/nfroidure/streamqueue/commit/962ec36e6ac22aa919cbe612fdc238f9ab42e3e6) Increasing the streamqueue version (@nfroidure) 61 | - [9bde919](https://github.com/nfroidure/streamqueue/commit/9bde9196911cef9a6222d3b35bdd72269d1d0c20) Fixing the regression closes #5 (@nfroidure) 62 | - [00f5d8c](https://github.com/nfroidure/streamqueue/commit/00f5d8ca458830100a78726e38ab3a19d3a65a04) Replacing setTimmediate per setTimeout in tests to match Node 0.8 (@nfroidure) 63 | - [5e1d91d](https://github.com/nfroidure/streamqueue/commit/5e1d91db0ad4c28e0ce436970a82915ec582c89e) Trying to change the ^ per ~ to fix the Node 0.8 build (@nfroidure) 64 | - [cc7858b](https://github.com/nfroidure/streamqueue/commit/cc7858b3ffcca8c51f1e54c7bcdcb0daa8a88034) Remove legacy streams wrappers since using readable-stream (@nfroidure) 65 | - [9aac64e](https://github.com/nfroidure/streamqueue/commit/9aac64ea2752b482ac731a5535ebd6523a3b4a61) Conflicts: src/index.js (@nfroidure) 66 | - [e85916e](https://github.com/nfroidure/streamqueue/commit/e85916e82deaf43d9d38b75e790d0bf4d99b3d97) Remove require of platform streams (@nfroidure) 67 | - [82b81fe](https://github.com/nfroidure/streamqueue/commit/82b81fedb0cda1c6a038c227adfabc93a8b8fcfc) Using isstream instead of custom function isaacs/readable-stream#87 (@nfroidure) 68 | - [9e92377](https://github.com/nfroidure/streamqueue/commit/9e923770c10a18a5c5f3831a81750d633b5c0b04) Updating dependencies (@nfroidure) 69 | - [a2324c8](https://github.com/nfroidure/streamqueue/commit/a2324c88fd91c8ccaabe3bf5ec7b9d298ee5a9ea) Fireing end asynchronously for consistencies with node streams #4 (@nfroidure) 70 | - [bb27bb8](https://github.com/nfroidure/streamqueue/commit/bb27bb8bc0b303dd311889912da99a82889e7223) Testing streamqueues inside streamqueues #4 (@nfroidure) 71 | - [92106fc](https://github.com/nfroidure/streamqueue/commit/92106fc4758e232560a0bf12694210e6e3c2c545) Execute end instead of firing the event (@nfroidure) 72 | - [1f1a917](https://github.com/nfroidure/streamqueue/commit/1f1a9174d466ea50499a4909f3cacbc0fea95ee4) Making .done() fire "end" asynchronously (@fidian) 73 | - [40bfb72](https://github.com/nfroidure/streamqueue/commit/40bfb72ca98845551cb928c2fb471a9141d90935) Version update (@nfroidure) 74 | - [21e2eee](https://github.com/nfroidure/streamqueue/commit/21e2eee2a6adc621b7659283c2db707fabac8029) iImproving performances by unpiping previously piped stream (@nfroidure) 75 | - [0a13b08](https://github.com/nfroidure/streamqueue/commit/0a13b08a8dd08f9934b1062eacc60526ab981d4c) Fixing the README file (@nfroidure) 76 | - [18b2347](https://github.com/nfroidure/streamqueue/commit/18b23470c78d5b9f74d82cd5b19f4105af9b69d2) New version 0.0.6 (@nfroidure) 77 | - [f75dc68](https://github.com/nfroidure/streamqueue/commit/f75dc6879204e4cc344fdeba6229d4e037324e2c) Added badges (@nfroidure) 78 | - [421923a](https://github.com/nfroidure/streamqueue/commit/421923aba4e61221676fb46150fc0502bd11f017) Dependencies update (@nfroidure) 79 | - [c79479a](https://github.com/nfroidure/streamqueue/commit/c79479a534e9e408a8c4264eb022510920e34ca8) Depending on readable stream closes #3 (@nfroidure) 80 | - [46b3e5f](https://github.com/nfroidure/streamqueue/commit/46b3e5f11d3152a8f18bfff599fa75c1bf99d41e) Accepting fucntion returning streams (@nfroidure) 81 | - [a355962](https://github.com/nfroidure/streamqueue/commit/a355962a5c70381c6ab1fcbaf978f687956f7e52) Ginving up 0.11. Waiting 0.12 (@nfroidure) 82 | - [29a15f2](https://github.com/nfroidure/streamqueue/commit/29a15f24f278128246db87732313e6de7cb8202a) Fix attempt for node 0.11 (@nfroidure) 83 | - [973c094](https://github.com/nfroidure/streamqueue/commit/973c094bbdf5110cdc17278d63ed5fb4e4da513a) Improved code coverage (@nfroidure) 84 | - [c3b0116](https://github.com/nfroidure/streamqueue/commit/c3b01164fbab579614f0ca455ee4addebf8280b6) Added coverage tests (@nfroidure) 85 | - [ecfdcc4](https://github.com/nfroidure/streamqueue/commit/ecfdcc4c8f2daf68bacb25035560eb7133c7e1b5) New verion 0.0.4 (@nfroidure) 86 | - [8d4a0f6](https://github.com/nfroidure/streamqueue/commit/8d4a0f6805cbd5c986127631bad05b796c84b572) Fix for objectMode old streams (@nfroidure) 87 | - [88f2e13](https://github.com/nfroidure/streamqueue/commit/88f2e13597c69f373b2f3d41737b7dbf4e5eeacd) Wrap old streams automatically (@nfroidure) 88 | - [b58e827](https://github.com/nfroidure/streamqueue/commit/b58e8275d2249179e58beb5544a523e804b17675) Deprecating pause option, pausing/resuming flowing mode streams automtically (@nfroidure) 89 | - [a5a7241](https://github.com/nfroidure/streamqueue/commit/a5a724160859486c93353e05995cbb9c1235f7a6) Added support for flowing mode, pause not aware streams (@nfroidure) 90 | - [0d71911](https://github.com/nfroidure/streamqueue/commit/0d71911526ac77988b5d4adfd8fa37f065fe83d4) Going to 0.0.2 (@nfroidure) 91 | - [d7adcdd](https://github.com/nfroidure/streamqueue/commit/d7adcdd8462af6555216c6da157027cc472098c6) Merging master (@nfroidure) 92 | - [86bd513](https://github.com/nfroidure/streamqueue/commit/86bd513b0b86074474f4f0c4e2983ae422b6b65d) Adding functionnal API + pause option (@nfroidure) 93 | - [#2](https://github.com/nfroidure/streamqueue/pull/2) Make new keyword optional (@darsain) 94 | - [9d18b32](https://github.com/nfroidure/streamqueue/commit/9d18b32ee86c4467f8ca070689f6204c2ab11f69) Make new keyword optional (@darsain) 95 | - [4b0f385](https://github.com/nfroidure/streamqueue/commit/4b0f3855e8f0768edd9ed247255224a01310a562) Fix the NPM module link (@nfroidure) 96 | - [e5a4ecd](https://github.com/nfroidure/streamqueue/commit/e5a4ecd105d727c4e23be3a45b3bf5fce75c96dc) Reemitting errors (@nfroidure) 97 | - [91c874e](https://github.com/nfroidure/streamqueue/commit/91c874e92d6e55842e19e4898641097856f49e54) Replacing end by done (@nfroidure) 98 | - [b578cf2](https://github.com/nfroidure/streamqueue/commit/b578cf26ea4b842e8f93bbc856079452a9405297) Improved the readme + added the cli tests command (@nfroidure) 99 | - [3b4d9cd](https://github.com/nfroidure/streamqueue/commit/3b4d9cd21916c1a4285e8ff51147b2556c68738d) First commit (@nfroidure) 100 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2013 Nicolas Froidure, 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /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 | # streamqueue 8 | > StreamQueue pipe the queued streams one by one in order to preserve their content order. 9 | 10 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/nfroidure/streamqueue/blob/main/LICENSE) 11 | 12 | 13 | [//]: # (::contents:start) 14 | 15 | ## Usage 16 | 17 | Install the [npm module](https://npmjs.org/package/streamqueue): 18 | 19 | ```sh 20 | npm install streamqueue --save 21 | ``` 22 | 23 | Then, in your scripts: 24 | 25 | ```js 26 | import { StreamQueue } from 'streamqueue'; 27 | import { createReadStream } from 'node:fs'; 28 | 29 | const queue = new StreamQueue( 30 | createReadStream('input.txt'), 31 | createReadStream('input2.txt'), 32 | createReadStream('input3.txt'), 33 | ).pipe(process.stdout); 34 | ``` 35 | 36 | StreamQueue also accept functions returning streams, the above can be written 37 | like this, doing system calls only when piping: 38 | 39 | ```js 40 | import { queueStreams } = require('streamqueue'); 41 | import { createReadStream } from 'node:fs'; 42 | 43 | const queue = queueStreams( 44 | createReadStream.bind(null, 'input.txt'), 45 | createReadStream.bind(null, 'input2.txt'), 46 | createReadStream.bind(null, 'input3.txt'), 47 | ).pipe(process.stdout); 48 | ``` 49 | 50 | Object-oriented traditionnal API offers more flexibility: 51 | 52 | ```js 53 | import { StreamQueue } from 'streamqueue'; 54 | import { createReadStream } from 'node:fs'; 55 | 56 | const queue = new StreamQueue(); 57 | 58 | queue.queue( 59 | createReadStream('input.txt'), 60 | createReadStream('input2.txt'), 61 | createReadStream('input3.txt'), 62 | ); 63 | queue.done(); 64 | 65 | queue.pipe(process.stdout); 66 | ``` 67 | 68 | You can also chain StreamQueue methods like that: 69 | 70 | ```js 71 | import StreamQueue from 'streamqueue'; 72 | import { createReadStream } from 'node:fs'; 73 | 74 | new StreamQueue() 75 | .queue(createReadStream('input.txt')) 76 | .queue(createReadStream('input2.txt')) 77 | .queue(createReadStream('input3.txt')) 78 | .done() 79 | .pipe(process.stdout); 80 | ``` 81 | 82 | You can queue new streams at any moment until you call the done() method. So the 83 | created stream will not fire the end event until done() call. 84 | 85 | ## Stats 86 | 87 | [![NPM](https://nodei.co/npm/streamqueue.png?downloads=true&stars=true)](https://nodei.co/npm/streamqueue/) 88 | [![NPM](https://nodei.co/npm-dl/streamqueue.png)](https://nodei.co/npm/streamqueue/) 89 | 90 | ## Contributing 91 | 92 | Feel free to propose your code if you agree with publishing it under the MIT 93 | license. 94 | 95 | [//]: # (::contents:end) 96 | 97 | # API 98 | ## Classes 99 | 100 |
101 |
StreamQueue
102 |

Pipe queued streams sequentially

103 |
104 |
105 | 106 | ## Functions 107 | 108 |
109 |
queueObjectStreams(options, ...streams)
110 |

Create a new queue in object mode and pipe given streams and end if some

111 |
112 |
queueStreams(options, ...streams)
113 |

Create a new queue and pipe given streams and end if some

114 |
115 |
116 | 117 | 118 | 119 | ## StreamQueue 120 | Pipe queued streams sequentially 121 | 122 | **Kind**: global class 123 | 124 | * [StreamQueue](#StreamQueue) 125 | * [new StreamQueue(options, ...streams)](#new_StreamQueue_new) 126 | * [.queue(...streams)](#StreamQueue+queue) ⇒ 127 | * [.done(...streams)](#StreamQueue+done) ⇒ 128 | 129 | 130 | 131 | ### new StreamQueue(options, ...streams) 132 | Create a new queue and pipe given streams and end if some 133 | 134 | **Returns**: StreamQueue 135 | 136 | | Param | Type | Description | 137 | | --- | --- | --- | 138 | | options | Object | The queue options | 139 | | options.objectMode | boolean | Operate in object mode | 140 | | options.pauseFlowingStream | boolean | Pause given streams that are flowing | 141 | | options.resumeFlowingStream | boolean | Resume given streams that are flowing | 142 | | ...streams | Readable \| function | The stream or stream returning function to pipe in | 143 | 144 | 145 | 146 | ### streamQueue.queue(...streams) ⇒ 147 | Queue each stream given in argument 148 | 149 | **Kind**: instance method of [StreamQueue](#StreamQueue) 150 | **Returns**: StreamQueue 151 | 152 | | Param | Type | Description | 153 | | --- | --- | --- | 154 | | ...streams | Readable \| function | The stream or stream returning function to pipe in | 155 | 156 | 157 | 158 | ### streamQueue.done(...streams) ⇒ 159 | Queue each stream given in argument and end 160 | 161 | **Kind**: instance method of [StreamQueue](#StreamQueue) 162 | **Returns**: StreamQueue 163 | 164 | | Param | Type | Description | 165 | | --- | --- | --- | 166 | | ...streams | Readable \| function | The stream or stream returning function to pipe in | 167 | 168 | 169 | 170 | ## queueObjectStreams(options, ...streams) ⇒ 171 | Create a new queue in object mode and pipe given streams and end if some 172 | 173 | **Kind**: global function 174 | **Returns**: StreamQueue 175 | 176 | | Param | Type | Description | 177 | | --- | --- | --- | 178 | | options | Object | The queue options | 179 | | ...streams | Readable \| function | The stream or stream returning function to pipe in | 180 | 181 | 182 | 183 | ## queueStreams(options, ...streams) ⇒ 184 | Create a new queue and pipe given streams and end if some 185 | 186 | **Kind**: global function 187 | **Returns**: StreamQueue 188 | 189 | | Param | Type | Description | 190 | | --- | --- | --- | 191 | | options | Object | The queue options | 192 | | ...streams | Readable \| function | The stream or stream returning function to pipe in | 193 | 194 | 195 | # Authors 196 | - [Nicolas Froidure](https://insertafter.com/en/index.html) 197 | 198 | # License 199 | [MIT](https://github.com/nfroidure/streamqueue/blob/main/LICENSE) 200 | -------------------------------------------------------------------------------- /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 | "tsesm", 7 | "jsdocs", 8 | "jest", 9 | "eslint", 10 | "codeclimate", 11 | "ghactions" 12 | ], 13 | "data": { 14 | "files": "'src/**/*.ts'", 15 | "testsFiles": "'src/**/*.tests.ts'", 16 | "distFiles": "'dist/**/*.js'", 17 | "ignore": [ 18 | "dist" 19 | ], 20 | "bundleFiles": [ 21 | "dist", 22 | "src" 23 | ] 24 | } 25 | }, 26 | "name": "streamqueue", 27 | "version": "2.0.0", 28 | "description": "StreamQueue pipe the queued streams one by one in order to preserve their content order.", 29 | "homepage": "https://github.com/nfroidure/StreamQueue", 30 | "main": "dist/index.js", 31 | "scripts": { 32 | "build": "rimraf 'dist' && tsc --outDir dist", 33 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md", 34 | "cli": "env NODE_ENV=${NODE_ENV:-cli}", 35 | "cover": "npm run jest -- --coverage", 36 | "cz": "env NODE_ENV=${NODE_ENV:-cli} git cz", 37 | "doc": "echo \"# API\" > API.md; jsdoc2md 'dist/**/*.js' >> API.md && git add API.md", 38 | "format": "npm run prettier", 39 | "jest": "NODE_OPTIONS=--experimental-vm-modules NODE_ENV=test jest", 40 | "lint": "eslint 'src/**/*.ts'", 41 | "metapak": "metapak", 42 | "precz": "npm t && npm run lint && npm run metapak -- -s && npm run build && npm run doc", 43 | "prettier": "prettier --write 'src/**/*.ts'", 44 | "preversion": "npm t && npm run lint && npm run build && npm run metapak -- -s && npm run doc", 45 | "rebuild": "swc ./src -s -d dist -C jsc.target=es2022", 46 | "test": "npm run jest", 47 | "type-check": "tsc --pretty --noEmit", 48 | "version": "npm run changelog && git add CHANGELOG.md" 49 | }, 50 | "repository": { 51 | "type": "git", 52 | "url": "git://github.com/nfroidure/StreamQueue" 53 | }, 54 | "keywords": [ 55 | "queue", 56 | "streaming", 57 | "stream", 58 | "async", 59 | "pipe" 60 | ], 61 | "dependencies": { 62 | "yerror": "^8.0.0" 63 | }, 64 | "devDependencies": { 65 | "@eslint/js": "^9.7.0", 66 | "@swc/cli": "^0.4.0", 67 | "@swc/core": "^1.6.13", 68 | "@swc/helpers": "^0.5.12", 69 | "@swc/jest": "^0.2.36", 70 | "commitizen": "^4.3.0", 71 | "conventional-changelog-cli": "^5.0.0", 72 | "cz-conventional-changelog": "^3.3.0", 73 | "eslint": "^9.7.0", 74 | "eslint-config-prettier": "^9.1.0", 75 | "eslint-plugin-jest": "^28.6.0", 76 | "eslint-plugin-prettier": "^5.1.3", 77 | "jest": "^29.7.0", 78 | "jsdoc-to-markdown": "^8.0.0", 79 | "metapak": "^6.0.1", 80 | "metapak-nfroidure": "^18.2.0", 81 | "prettier": "^3.3.3", 82 | "rimraf": "^6.0.1", 83 | "streamtest": "^3.0.1", 84 | "typescript": "^5.5.3", 85 | "typescript-eslint": "^7.16.0" 86 | }, 87 | "author": { 88 | "name": "Nicolas Froidure", 89 | "email": "nicolas.froidure@insertafter.com", 90 | "url": "https://insertafter.com/en/index.html" 91 | }, 92 | "engines": { 93 | "node": ">=20.11.1" 94 | }, 95 | "licenses": [ 96 | { 97 | "type": "MIT", 98 | "url": "https://github.com/nfroidure/StreamQueue/blob/master/LICENSE" 99 | } 100 | ], 101 | "bugs": { 102 | "url": "https://github.com/nfroidure/StreamQueue/issues" 103 | }, 104 | "license": "MIT", 105 | "config": { 106 | "commitizen": { 107 | "path": "./node_modules/cz-conventional-changelog" 108 | } 109 | }, 110 | "greenkeeper": { 111 | "ignore": [ 112 | "commitizen", 113 | "cz-conventional-changelog", 114 | "conventional-changelog-cli", 115 | "typescript", 116 | "rimraf", 117 | "@swc/cli", 118 | "@swc/core", 119 | "@swc/helpers", 120 | "jsdoc-to-markdown", 121 | "jest", 122 | "@swc/jest", 123 | "eslint", 124 | "prettier", 125 | "eslint-config-prettier", 126 | "eslint-plugin-prettier", 127 | "typescript-eslint" 128 | ] 129 | }, 130 | "contributors": [], 131 | "files": [ 132 | "dist", 133 | "src", 134 | "LICENSE", 135 | "README.md", 136 | "CHANGELOG.md" 137 | ], 138 | "type": "module", 139 | "types": "dist/index.d.ts", 140 | "jest": { 141 | "coverageReporters": [ 142 | "lcov" 143 | ], 144 | "testPathIgnorePatterns": [ 145 | "/node_modules/" 146 | ], 147 | "roots": [ 148 | "/src" 149 | ], 150 | "transform": { 151 | "^.+\\.tsx?$": [ 152 | "@swc/jest", 153 | {} 154 | ] 155 | }, 156 | "testEnvironment": "node", 157 | "moduleNameMapper": { 158 | "(.+)\\.js": "$1" 159 | }, 160 | "extensionsToTreatAsEsm": [ 161 | ".ts" 162 | ], 163 | "prettierPath": null 164 | }, 165 | "overrides": { 166 | "eslint": "^9.7.0" 167 | }, 168 | "prettier": { 169 | "semi": true, 170 | "printWidth": 80, 171 | "singleQuote": true, 172 | "trailingComma": "all", 173 | "proseWrap": "always" 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from '@jest/globals'; 2 | import { YError } from 'yerror'; 3 | import { PassThrough } from 'node:stream'; 4 | import StreamTest from 'streamtest'; 5 | import { StreamQueue, queueStreams, queueObjectStreams } from './index.js'; 6 | 7 | // Tests 8 | describe('StreamQueue', () => { 9 | describe('in binary mode', () => { 10 | describe('and with async streams', () => { 11 | test('should work with functionnal API', async () => { 12 | const [stream, result] = StreamTest.toText(); 13 | 14 | queueStreams( 15 | StreamTest.fromChunks([Buffer.from('wa'), Buffer.from('dup')]), 16 | StreamTest.fromChunks([Buffer.from('pl'), Buffer.from('op')]), 17 | StreamTest.fromChunks([ 18 | Buffer.from('ki'), 19 | Buffer.from('koo'), 20 | Buffer.from('lol'), 21 | ]), 22 | ).pipe(stream); 23 | expect(await result).toEqual('wadupplopkikoolol'); 24 | }); 25 | 26 | test('should work with functionnal API and options', async () => { 27 | const [stream, result] = StreamTest.toText(); 28 | 29 | queueStreams( 30 | {}, 31 | StreamTest.fromChunks([Buffer.from('wa'), Buffer.from('dup')]), 32 | StreamTest.fromChunks([Buffer.from('pl'), Buffer.from('op')]), 33 | StreamTest.fromChunks([ 34 | Buffer.from('ki'), 35 | Buffer.from('koo'), 36 | Buffer.from('lol'), 37 | ]), 38 | ).pipe(stream); 39 | expect(await result).toEqual('wadupplopkikoolol'); 40 | }); 41 | 42 | test('should work with POO API', async () => { 43 | const queue = new StreamQueue(); 44 | const [stream, result] = StreamTest.toText(); 45 | 46 | queue.queue( 47 | StreamTest.fromChunks([Buffer.from('wa'), Buffer.from('dup')]), 48 | ); 49 | queue.queue( 50 | StreamTest.fromChunks([Buffer.from('pl'), Buffer.from('op')]), 51 | ); 52 | queue.queue( 53 | StreamTest.fromChunks([ 54 | Buffer.from('ki'), 55 | Buffer.from('koo'), 56 | Buffer.from('lol'), 57 | ]), 58 | ); 59 | expect(queue.length).toEqual(3); 60 | queue.pipe(stream); 61 | queue.done(); 62 | expect(await result).toEqual('wadupplopkikoolol'); 63 | }); 64 | 65 | test('should pause streams in flowing mode', async () => { 66 | const [stream, result] = StreamTest.toText(); 67 | const queue = new StreamQueue({ 68 | pauseFlowingStream: true, 69 | resumeFlowingStream: true, 70 | }); 71 | const flowingStream = StreamTest.fromChunks([ 72 | Buffer.from('pl'), 73 | Buffer.from('op'), 74 | ]); 75 | 76 | flowingStream.on('data', () => {}); 77 | queue.queue( 78 | StreamTest.fromChunks([Buffer.from('wa'), Buffer.from('dup')]), 79 | ); 80 | queue.queue(flowingStream); 81 | queue.queue( 82 | StreamTest.fromChunks([ 83 | Buffer.from('ki'), 84 | Buffer.from('koo'), 85 | Buffer.from('lol'), 86 | ]), 87 | ); 88 | expect(queue.length).toEqual(3); 89 | queue.pipe(stream); 90 | queue.done(); 91 | expect(await result).toEqual('wadupplopkikoolol'); 92 | }); 93 | 94 | test('should work with POO API and options', async () => { 95 | const [stream, result] = StreamTest.toText(); 96 | const queue = new StreamQueue({ 97 | pauseFlowingStream: true, 98 | resumeFlowingStream: true, 99 | }); 100 | 101 | queue.queue( 102 | StreamTest.fromChunks([Buffer.from('wa'), Buffer.from('dup')]), 103 | ); 104 | queue.queue( 105 | StreamTest.fromChunks([Buffer.from('pl'), Buffer.from('op')]), 106 | ); 107 | queue.queue( 108 | StreamTest.fromChunks([ 109 | Buffer.from('ki'), 110 | Buffer.from('koo'), 111 | Buffer.from('lol'), 112 | ]), 113 | ); 114 | expect(queue.length).toEqual(3); 115 | queue.pipe(stream); 116 | queue.done(); 117 | expect(await result).toEqual('wadupplopkikoolol'); 118 | }); 119 | 120 | test('should work with POO API and a late done call', async () => { 121 | const [stream, result] = StreamTest.toText(); 122 | const queue = new StreamQueue(); 123 | 124 | queue.queue( 125 | StreamTest.fromChunks([Buffer.from('wa'), Buffer.from('dup')]), 126 | ); 127 | queue.queue( 128 | StreamTest.fromChunks([Buffer.from('pl'), Buffer.from('op')]), 129 | ); 130 | queue.queue( 131 | StreamTest.fromChunks([ 132 | Buffer.from('ki'), 133 | Buffer.from('koo'), 134 | Buffer.from('lol'), 135 | ]), 136 | ); 137 | expect(queue.length).toEqual(3); 138 | queue.pipe(stream); 139 | setTimeout(() => { 140 | queue.done(); 141 | }, 100); 142 | expect(await result).toEqual('wadupplopkikoolol'); 143 | }); 144 | 145 | test('should work with POO API and no stream plus sync done', async () => { 146 | const [stream, result] = StreamTest.toText(); 147 | const queue = new StreamQueue(); 148 | 149 | expect(queue.length).toEqual(0); 150 | queue.queue(); 151 | queue.pipe(stream); 152 | queue.done(); 153 | expect(await result).toEqual(''); 154 | }); 155 | 156 | test('should work with POO API and no stream plus async done', async () => { 157 | const [stream, result] = StreamTest.toText(); 158 | const queue = new StreamQueue(); 159 | 160 | expect(queue.length).toEqual(0); 161 | queue.queue(); 162 | queue.pipe(stream); 163 | setTimeout(() => { 164 | queue.done(); 165 | }, 100); 166 | expect(await result).toEqual(''); 167 | }); 168 | 169 | test('should work with POO API and a streamqueue stream plus async done', async () => { 170 | const [stream, result] = StreamTest.toText(); 171 | const queue = new StreamQueue(); 172 | const child = new StreamQueue(); 173 | 174 | queue.queue(child); 175 | expect(queue.length).toEqual(1); 176 | queue.pipe(stream); 177 | child.done(); 178 | setTimeout(() => { 179 | queue.done(); 180 | }, 100); 181 | expect(await result).toEqual(''); 182 | }); 183 | 184 | test('should work with POO API and a streamqueue stream plus async done', async () => { 185 | const [stream, result] = StreamTest.toText(); 186 | const queue = new StreamQueue(); 187 | const child = new StreamQueue(); 188 | 189 | queue.queue(child); 190 | expect(queue.length).toEqual(1); 191 | queue.pipe(stream); 192 | child.done(); 193 | queue.done(); 194 | expect(await result).toEqual(''); 195 | }); 196 | 197 | test('should work with POO API and a streamqueue ended stream plus async done', async () => { 198 | const [stream, result] = StreamTest.toText(); 199 | const queue = new StreamQueue(); 200 | const child = new StreamQueue(); 201 | 202 | queue.queue(child); 203 | child.done(); 204 | expect(queue.length).toEqual(1); 205 | queue.pipe(stream); 206 | setTimeout(() => { 207 | queue.done(); 208 | }, 100); 209 | expect(await result).toEqual(''); 210 | }); 211 | 212 | test('should fire end asynchronously with streams', async () => { 213 | const [stream, result] = StreamTest.toText(); 214 | const queue = new StreamQueue(); 215 | let ended = false; 216 | 217 | queue.queue( 218 | StreamTest.fromChunks([Buffer.from('wa'), Buffer.from('dup')]).on( 219 | 'end', 220 | () => { 221 | expect(ended).toEqual(false); 222 | }, 223 | ), 224 | ); 225 | queue.queue( 226 | StreamTest.fromChunks([Buffer.from('pl'), Buffer.from('op')]).on( 227 | 'end', 228 | () => { 229 | expect(ended).toEqual(false); 230 | }, 231 | ), 232 | ); 233 | queue.queue( 234 | StreamTest.fromChunks([ 235 | Buffer.from('ki'), 236 | Buffer.from('koo'), 237 | Buffer.from('lol'), 238 | ]).on('end', () => { 239 | expect(ended).toEqual(false); 240 | }), 241 | ); 242 | expect(queue.length).toEqual(3); 243 | queue.pipe(stream); 244 | queue.on('end', () => { 245 | ended = true; 246 | }); 247 | queue.done(); 248 | expect(ended).toEqual(false); 249 | expect(await result).toEqual('wadupplopkikoolol'); 250 | }); 251 | 252 | test('should fire end asynchronously when empty', async () => { 253 | const [stream, result] = StreamTest.toText(); 254 | const queue = new StreamQueue(); 255 | let ended = false; 256 | 257 | expect(queue.length).toEqual(0); 258 | queue.pipe(stream); 259 | queue.on('end', () => { 260 | ended = true; 261 | }); 262 | queue.done(); 263 | expect(ended).toEqual(false); 264 | expect(await result).toEqual(''); 265 | }); 266 | 267 | test('should work with POO API and a streamqueue ended stream plus sync done', async () => { 268 | const [stream, result] = StreamTest.toText(); 269 | const queue = new StreamQueue(); 270 | const child = new StreamQueue(); 271 | 272 | queue.queue(child); 273 | child.done(); 274 | expect(queue.length).toEqual(1); 275 | queue.pipe(stream); 276 | queue.done(); 277 | expect(await result).toEqual(''); 278 | }); 279 | 280 | test('should work with POO API and a streamqueue ended stream plus async done', async () => { 281 | const [stream, result] = StreamTest.toText(); 282 | const queue = new StreamQueue(); 283 | const child = new StreamQueue(); 284 | 285 | child.done(); 286 | queue.queue(child); 287 | expect(queue.length).toEqual(1); 288 | queue.pipe(stream); 289 | setTimeout(() => { 290 | queue.done(); 291 | }, 100); 292 | expect(await result).toEqual(''); 293 | }); 294 | 295 | test('should work with POO API and a streamqueue ended stream plus sync done', async () => { 296 | const [stream, result] = StreamTest.toText(); 297 | const queue = new StreamQueue(); 298 | const child = new StreamQueue(); 299 | 300 | child.done(); 301 | queue.queue(child); 302 | expect(queue.length).toEqual(1); 303 | queue.pipe(stream); 304 | queue.done(); 305 | expect(await result).toEqual(''); 306 | }); 307 | 308 | test('should reemit errors', async () => { 309 | const [stream, result] = StreamTest.toText(); 310 | const queue = new StreamQueue(); 311 | let _err; 312 | 313 | queue.queue(StreamTest.fromErroredChunks(new YError('E_ERROR'), [])); 314 | queue.queue( 315 | StreamTest.fromChunks([Buffer.from('wa'), Buffer.from('dup')]), 316 | ); 317 | queue.queue( 318 | StreamTest.fromChunks([Buffer.from('pl'), Buffer.from('op')]), 319 | ); 320 | queue.queue( 321 | StreamTest.fromChunks([ 322 | Buffer.from('ki'), 323 | Buffer.from('koo'), 324 | Buffer.from('lol'), 325 | ]), 326 | ); 327 | expect(queue.length).toEqual(4); 328 | queue.on('error', (err) => { 329 | _err = err; 330 | }); 331 | queue.pipe(stream); 332 | queue.done(); 333 | expect(await result).toEqual('wadupplopkikoolol'); 334 | expect(_err).toBeTruthy(); 335 | expect(_err.message).toEqual('E_ERROR'); 336 | }); 337 | 338 | test('should reemit errors elsewhere', async () => { 339 | const [stream, result] = StreamTest.toText(); 340 | const queue = new StreamQueue(); 341 | let _err; 342 | 343 | queue.queue( 344 | StreamTest.fromChunks([Buffer.from('wa'), Buffer.from('dup')]), 345 | ); 346 | queue.queue( 347 | StreamTest.fromChunks([Buffer.from('pl'), Buffer.from('op')]), 348 | ); 349 | queue.queue(StreamTest.fromErroredChunks(new YError('E_ERROR'), [])); 350 | queue.queue( 351 | StreamTest.fromChunks([ 352 | Buffer.from('ki'), 353 | Buffer.from('koo'), 354 | Buffer.from('lol'), 355 | ]), 356 | ); 357 | expect(queue.length).toEqual(4); 358 | queue.on('error', (err) => { 359 | _err = err; 360 | }); 361 | queue.pipe(stream); 362 | queue.done(); 363 | expect(await result).toEqual('wadupplopkikoolol'); 364 | expect(_err).toBeTruthy(); 365 | expect(_err.message).toEqual('E_ERROR'); 366 | }); 367 | }); 368 | 369 | describe('and with sync streams', () => { 370 | test('should work with functionnal API', async () => { 371 | const [stream, result] = StreamTest.toText(); 372 | const stream1 = new PassThrough(); 373 | const stream2 = new PassThrough(); 374 | const stream3 = new PassThrough(); 375 | 376 | queueStreams({}, stream1, stream2, stream3).pipe(stream); 377 | 378 | for (const buffer of [Buffer.from('wa'), Buffer.from('dup')]) { 379 | stream1.write(buffer); 380 | } 381 | stream1.end(); 382 | for (const buffer of [Buffer.from('pl'), Buffer.from('op')]) { 383 | stream2.write(buffer); 384 | } 385 | stream2.end(); 386 | for (const buffer of [ 387 | Buffer.from('ki'), 388 | Buffer.from('koo'), 389 | Buffer.from('lol'), 390 | ]) { 391 | stream3.write(buffer); 392 | } 393 | stream3.end(); 394 | 395 | expect(await result).toEqual('wadupplopkikoolol'); 396 | }); 397 | 398 | test('should work with POO API', async () => { 399 | const [stream, result] = StreamTest.toText(); 400 | const queue = new StreamQueue(); 401 | const stream1 = new PassThrough(); 402 | const stream2 = new PassThrough(); 403 | const stream3 = new PassThrough(); 404 | 405 | queue.queue(stream1); 406 | queue.queue(stream2); 407 | queue.queue(stream3); 408 | 409 | for (const buffer of [Buffer.from('wa'), Buffer.from('dup')]) { 410 | stream1.write(buffer); 411 | } 412 | stream1.end(); 413 | for (const buffer of [Buffer.from('pl'), Buffer.from('op')]) { 414 | stream2.write(buffer); 415 | } 416 | stream2.end(); 417 | for (const buffer of [ 418 | Buffer.from('ki'), 419 | Buffer.from('koo'), 420 | Buffer.from('lol'), 421 | ]) { 422 | stream3.write(buffer); 423 | } 424 | stream3.end(); 425 | 426 | expect(queue.length).toEqual(3); 427 | queue.pipe(stream); 428 | queue.done(); 429 | expect(await result).toEqual('wadupplopkikoolol'); 430 | }); 431 | 432 | test('should emit an error when calling done twice', async () => { 433 | const [stream, result] = StreamTest.toText(); 434 | const queue = new StreamQueue(); 435 | const stream1 = new PassThrough(); 436 | const stream2 = new PassThrough(); 437 | const stream3 = new PassThrough(); 438 | 439 | queue.queue(stream1); 440 | queue.queue(stream2); 441 | queue.queue(stream3); 442 | 443 | for (const buffer of [Buffer.from('wa'), Buffer.from('dup')]) { 444 | stream1.write(buffer); 445 | } 446 | stream1.end(); 447 | for (const buffer of [Buffer.from('pl'), Buffer.from('op')]) { 448 | stream2.write(buffer); 449 | } 450 | stream2.end(); 451 | for (const buffer of [ 452 | Buffer.from('ki'), 453 | Buffer.from('koo'), 454 | Buffer.from('lol'), 455 | ]) { 456 | stream3.write(buffer); 457 | } 458 | stream3.end(); 459 | 460 | expect(queue.length).toEqual(3); 461 | queue.pipe(stream); 462 | queue.done(); 463 | try { 464 | queue.done(); 465 | throw new YError('E_UNEXPECTED_SUCCESS'); 466 | } catch (err) { 467 | expect((err as YError).code).toEqual('E_QUEUE_ALREADY_ENDED'); 468 | } 469 | expect(await result).toEqual('wadupplopkikoolol'); 470 | }); 471 | 472 | test('should emit an error when queueing after done was called', async () => { 473 | const [stream, result] = StreamTest.toText(); 474 | const queue = new StreamQueue(); 475 | const stream1 = new PassThrough(); 476 | const stream2 = new PassThrough(); 477 | const stream3 = new PassThrough(); 478 | 479 | queue.queue(stream1); 480 | queue.queue(stream2); 481 | queue.queue(stream3); 482 | 483 | for (const buffer of [Buffer.from('wa'), Buffer.from('dup')]) { 484 | stream1.write(buffer); 485 | } 486 | stream1.end(); 487 | for (const buffer of [Buffer.from('pl'), Buffer.from('op')]) { 488 | stream2.write(buffer); 489 | } 490 | stream2.end(); 491 | for (const buffer of [ 492 | Buffer.from('ki'), 493 | Buffer.from('koo'), 494 | Buffer.from('lol'), 495 | ]) { 496 | stream3.write(buffer); 497 | } 498 | stream3.end(); 499 | 500 | expect(queue.length).toEqual(3); 501 | queue.pipe(stream); 502 | queue.done(); 503 | try { 504 | queue.queue(new PassThrough()); 505 | throw new YError('E_UNEXPECTED_SUCCESS'); 506 | } catch (err) { 507 | expect((err as YError).code).toEqual('E_QUEUE_ALREADY_ENDED'); 508 | } 509 | expect(await result).toEqual('wadupplopkikoolol'); 510 | }); 511 | 512 | test('should reemit errors', async () => { 513 | const [stream, result] = StreamTest.toText(); 514 | let _err; 515 | const queue = new StreamQueue(); 516 | const stream1 = new PassThrough(); 517 | const stream2 = new PassThrough(); 518 | const stream3 = new PassThrough(); 519 | const stream4 = new PassThrough(); 520 | 521 | queue.queue(stream1); 522 | queue.queue(stream2); 523 | queue.queue(stream3); 524 | queue.queue(stream4); 525 | queue.on('error', (err) => { 526 | _err = err; 527 | }); 528 | 529 | stream1.emit('error', new YError('E_ERROR')); 530 | stream1.end(); 531 | 532 | for (const buffer of [Buffer.from('wa'), Buffer.from('dup')]) { 533 | stream2.write(buffer); 534 | } 535 | stream2.end(); 536 | for (const buffer of [Buffer.from('pl'), Buffer.from('op')]) { 537 | stream3.write(buffer); 538 | } 539 | stream3.end(); 540 | for (const buffer of [ 541 | Buffer.from('ki'), 542 | Buffer.from('koo'), 543 | Buffer.from('lol'), 544 | ]) { 545 | stream4.write(buffer); 546 | } 547 | stream4.end(); 548 | 549 | expect(queue.length).toEqual(4); 550 | queue.pipe(stream); 551 | queue.done(); 552 | expect(await result).toEqual('wadupplopkikoolol'); 553 | expect(_err).toBeTruthy(); 554 | expect(_err.message).toEqual('E_ERROR'); 555 | }); 556 | }); 557 | 558 | describe('and with functions returning streams', () => { 559 | test('should work with functionnal API', async () => { 560 | const [stream, result] = StreamTest.toText(); 561 | 562 | queueStreams( 563 | {}, 564 | StreamTest.fromChunks.bind(null, [ 565 | Buffer.from('wa'), 566 | Buffer.from('dup'), 567 | ]), 568 | StreamTest.fromChunks.bind(null, [ 569 | Buffer.from('pl'), 570 | Buffer.from('op'), 571 | ]), 572 | StreamTest.fromChunks.bind(null, [ 573 | Buffer.from('ki'), 574 | Buffer.from('koo'), 575 | Buffer.from('lol'), 576 | ]), 577 | ).pipe(stream); 578 | expect(await result).toEqual('wadupplopkikoolol'); 579 | }); 580 | 581 | test('should work with functionnal API and options', async () => { 582 | const [stream, result] = StreamTest.toText(); 583 | 584 | queueStreams( 585 | StreamTest.fromChunks.bind(null, [ 586 | Buffer.from('wa'), 587 | Buffer.from('dup'), 588 | ]), 589 | StreamTest.fromChunks.bind(null, [ 590 | Buffer.from('pl'), 591 | Buffer.from('op'), 592 | ]), 593 | StreamTest.fromChunks.bind(null, [ 594 | Buffer.from('ki'), 595 | Buffer.from('koo'), 596 | Buffer.from('lol'), 597 | ]), 598 | ).pipe(stream); 599 | expect(await result).toEqual('wadupplopkikoolol'); 600 | }); 601 | 602 | test('should work with POO API', async () => { 603 | const [stream, result] = StreamTest.toText(); 604 | const queue = new StreamQueue(); 605 | 606 | queue.queue( 607 | StreamTest.fromChunks.bind(null, [ 608 | Buffer.from('wa'), 609 | Buffer.from('dup'), 610 | ]), 611 | ); 612 | queue.queue( 613 | StreamTest.fromChunks.bind(null, [ 614 | Buffer.from('pl'), 615 | Buffer.from('op'), 616 | ]), 617 | ); 618 | queue.queue( 619 | StreamTest.fromChunks.bind(null, [ 620 | Buffer.from('ki'), 621 | Buffer.from('koo'), 622 | Buffer.from('lol'), 623 | ]), 624 | ); 625 | expect(queue.length).toEqual(3); 626 | queue.pipe(stream); 627 | queue.done(); 628 | expect(await result).toEqual('wadupplopkikoolol'); 629 | }); 630 | 631 | test('should pause streams in flowing mode', async () => { 632 | const [stream, result] = StreamTest.toText(); 633 | const queue = new StreamQueue({ 634 | pauseFlowingStream: true, 635 | resumeFlowingStream: true, 636 | }); 637 | 638 | queue.queue( 639 | StreamTest.fromChunks.bind(null, [ 640 | Buffer.from('wa'), 641 | Buffer.from('dup'), 642 | ]), 643 | ); 644 | queue.queue(() => { 645 | const stream = StreamTest.fromChunks([ 646 | Buffer.from('pl'), 647 | Buffer.from('op'), 648 | ]); 649 | 650 | stream.on('data', () => {}); 651 | return stream; 652 | }); 653 | queue.queue( 654 | StreamTest.fromChunks.bind(null, [ 655 | Buffer.from('ki'), 656 | Buffer.from('koo'), 657 | Buffer.from('lol'), 658 | ]), 659 | ); 660 | expect(queue.length).toEqual(3); 661 | queue.pipe(stream); 662 | queue.done(); 663 | expect(await result).toEqual('wadupplopkikoolol'); 664 | }); 665 | 666 | test('should work with POO API and options', async () => { 667 | const [stream, result] = StreamTest.toText(); 668 | const queue = new StreamQueue({ 669 | pauseFlowingStream: true, 670 | resumeFlowingStream: true, 671 | }); 672 | 673 | queue.queue( 674 | StreamTest.fromChunks.bind(null, [ 675 | Buffer.from('wa'), 676 | Buffer.from('dup'), 677 | ]), 678 | ); 679 | queue.queue( 680 | StreamTest.fromChunks.bind(null, [ 681 | Buffer.from('pl'), 682 | Buffer.from('op'), 683 | ]), 684 | ); 685 | queue.queue( 686 | StreamTest.fromChunks.bind(null, [ 687 | Buffer.from('ki'), 688 | Buffer.from('koo'), 689 | Buffer.from('lol'), 690 | ]), 691 | ); 692 | expect(queue.length).toEqual(3); 693 | queue.pipe(stream); 694 | queue.done(); 695 | expect(await result).toEqual('wadupplopkikoolol'); 696 | }); 697 | 698 | test('should work with POO API and a late done call', async () => { 699 | const [stream, result] = StreamTest.toText(); 700 | const queue = new StreamQueue(); 701 | 702 | queue.queue( 703 | StreamTest.fromChunks.bind(null, [ 704 | Buffer.from('wa'), 705 | Buffer.from('dup'), 706 | ]), 707 | ); 708 | queue.queue( 709 | StreamTest.fromChunks.bind(null, [ 710 | Buffer.from('pl'), 711 | Buffer.from('op'), 712 | ]), 713 | ); 714 | queue.queue( 715 | StreamTest.fromChunks.bind(null, [ 716 | Buffer.from('ki'), 717 | Buffer.from('koo'), 718 | Buffer.from('lol'), 719 | ]), 720 | ); 721 | expect(queue.length).toEqual(3); 722 | queue.pipe(stream); 723 | setTimeout(() => { 724 | queue.done(); 725 | }, 100); 726 | expect(await result).toEqual('wadupplopkikoolol'); 727 | }); 728 | 729 | test('should reemit errors', async () => { 730 | const [stream, result] = StreamTest.toText(); 731 | let _err; 732 | const queue = new StreamQueue(); 733 | 734 | queue.queue( 735 | StreamTest.fromErroredChunks.bind(null, new YError('E_ERROR'), []), 736 | ); 737 | queue.queue( 738 | StreamTest.fromChunks.bind(null, [ 739 | Buffer.from('wa'), 740 | Buffer.from('dup'), 741 | ]), 742 | ); 743 | queue.queue( 744 | StreamTest.fromChunks.bind(null, [ 745 | Buffer.from('pl'), 746 | Buffer.from('op'), 747 | ]), 748 | ); 749 | queue.queue( 750 | StreamTest.fromChunks.bind(null, [ 751 | Buffer.from('ki'), 752 | Buffer.from('koo'), 753 | Buffer.from('lol'), 754 | ]), 755 | ); 756 | expect(queue.length).toEqual(4); 757 | queue.on('error', (err) => { 758 | _err = err; 759 | }); 760 | queue.pipe(stream); 761 | queue.done(); 762 | expect(await result).toEqual('wadupplopkikoolol'); 763 | expect(_err).toBeTruthy(); 764 | expect(_err.message).toEqual('E_ERROR'); 765 | }); 766 | 767 | test('should reemit errors elsewhere', async () => { 768 | const [stream, result] = StreamTest.toText(); 769 | let _err; 770 | const queue = new StreamQueue(); 771 | 772 | queue.queue( 773 | StreamTest.fromChunks.bind(null, [ 774 | Buffer.from('wa'), 775 | Buffer.from('dup'), 776 | ]), 777 | ); 778 | queue.queue( 779 | StreamTest.fromChunks.bind(null, [ 780 | Buffer.from('pl'), 781 | Buffer.from('op'), 782 | ]), 783 | ); 784 | queue.queue( 785 | StreamTest.fromErroredChunks.bind(null, new YError('E_ERROR'), []), 786 | ); 787 | queue.queue( 788 | StreamTest.fromChunks.bind(null, [ 789 | Buffer.from('ki'), 790 | Buffer.from('koo'), 791 | Buffer.from('lol'), 792 | ]), 793 | ); 794 | expect(queue.length).toEqual(4); 795 | queue.on('error', (err) => { 796 | _err = err; 797 | }); 798 | queue.pipe(stream); 799 | queue.done(); 800 | expect(await result).toEqual('wadupplopkikoolol'); 801 | expect(_err).toBeTruthy(); 802 | expect(_err.message).toEqual('E_ERROR'); 803 | }); 804 | }); 805 | }); 806 | 807 | describe('in object mode', () => { 808 | test('should work', async () => { 809 | const [stream, result] = StreamTest.toObjects(); 810 | const queue = new StreamQueue({ objectMode: true }); 811 | 812 | queue.queue(StreamTest.fromObjects([{ s: 'wa' }, { s: 'dup' }])); 813 | queue.queue(StreamTest.fromObjects([{ s: 'pl' }, { s: 'op' }])); 814 | queue.queue( 815 | StreamTest.fromObjects([{ s: 'ki' }, { s: 'koo' }, { s: 'lol' }]), 816 | ); 817 | queue.pipe(stream); 818 | queue.done(); 819 | expect(await result).toEqual([ 820 | { s: 'wa' }, 821 | { s: 'dup' }, 822 | { s: 'pl' }, 823 | { s: 'op' }, 824 | { s: 'ki' }, 825 | { s: 'koo' }, 826 | { s: 'lol' }, 827 | ]); 828 | }); 829 | }); 830 | 831 | describe('in object mode with the object mode shortcut', () => { 832 | test('should work without options', async () => { 833 | const [stream, result] = StreamTest.toObjects(); 834 | queueObjectStreams( 835 | StreamTest.fromObjects([{ s: 'wa' }, { s: 'dup' }]), 836 | StreamTest.fromObjects([{ s: 'pl' }, { s: 'op' }]), 837 | StreamTest.fromObjects([{ s: 'ki' }, { s: 'koo' }, { s: 'lol' }]), 838 | ).pipe(stream); 839 | expect(await result).toEqual([ 840 | { s: 'wa' }, 841 | { s: 'dup' }, 842 | { s: 'pl' }, 843 | { s: 'op' }, 844 | { s: 'ki' }, 845 | { s: 'koo' }, 846 | { s: 'lol' }, 847 | ]); 848 | }); 849 | 850 | test('should work with options', async () => { 851 | const [stream, result] = StreamTest.toObjects(); 852 | queueObjectStreams( 853 | {}, 854 | StreamTest.fromObjects([{ s: 'wa' }, { s: 'dup' }]), 855 | StreamTest.fromObjects([{ s: 'pl' }, { s: 'op' }]), 856 | StreamTest.fromObjects([{ s: 'ki' }, { s: 'koo' }, { s: 'lol' }]), 857 | ).pipe(stream); 858 | expect(await result).toEqual([ 859 | { s: 'wa' }, 860 | { s: 'dup' }, 861 | { s: 'pl' }, 862 | { s: 'op' }, 863 | { s: 'ki' }, 864 | { s: 'koo' }, 865 | { s: 'lol' }, 866 | ]); 867 | }); 868 | 869 | test('should work without options nor streams', async () => { 870 | const [stream, result] = StreamTest.toObjects(); 871 | const queue = queueObjectStreams(); 872 | 873 | queue.queue(StreamTest.fromObjects([{ s: 'wa' }, { s: 'dup' }])); 874 | queue.queue(StreamTest.fromObjects([{ s: 'pl' }, { s: 'op' }])); 875 | queue.queue( 876 | StreamTest.fromObjects([{ s: 'ki' }, { s: 'koo' }, { s: 'lol' }]), 877 | ); 878 | queue.done(); 879 | queue.pipe(stream); 880 | expect(await result).toEqual([ 881 | { s: 'wa' }, 882 | { s: 'dup' }, 883 | { s: 'pl' }, 884 | { s: 'op' }, 885 | { s: 'ki' }, 886 | { s: 'koo' }, 887 | { s: 'lol' }, 888 | ]); 889 | }); 890 | 891 | test('should work with options and no streams', async () => { 892 | const [stream, result] = StreamTest.toObjects(); 893 | const queue = queueObjectStreams({}); 894 | 895 | queue.queue(StreamTest.fromObjects([{ s: 'wa' }, { s: 'dup' }])); 896 | queue.queue(StreamTest.fromObjects([{ s: 'pl' }, { s: 'op' }])); 897 | queue.queue( 898 | StreamTest.fromObjects([{ s: 'ki' }, { s: 'koo' }, { s: 'lol' }]), 899 | ); 900 | queue.done(); 901 | queue.pipe(stream); 902 | expect(await result).toEqual([ 903 | { s: 'wa' }, 904 | { s: 'dup' }, 905 | { s: 'pl' }, 906 | { s: 'op' }, 907 | { s: 'ki' }, 908 | { s: 'koo' }, 909 | { s: 'lol' }, 910 | ]); 911 | }); 912 | }); 913 | }); 914 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Writable, Readable, Stream } from 'stream'; 2 | import { YError } from 'yerror'; 3 | 4 | export type StreamQueueOptions = { 5 | objectMode: boolean; 6 | pauseFlowingStream: boolean; 7 | resumeFlowingStream: boolean; 8 | }; 9 | export type StreamQueueFunction = () => Readable; 10 | 11 | const DEFAULT_OPTIONS: StreamQueueOptions = { 12 | objectMode: false, 13 | pauseFlowingStream: true, 14 | resumeFlowingStream: true, 15 | }; 16 | 17 | /** Pipe queued streams sequentially */ 18 | class StreamQueue extends Readable { 19 | private _options: StreamQueueOptions; 20 | private _streams: Readable[] = []; 21 | private _running: boolean = false; 22 | private _ending: boolean = false; 23 | private _awaitDrain: null | ((error?: Error | null | undefined) => void) = 24 | null; 25 | private _internalStream: Writable; 26 | private _curStream: Readable | null = null; 27 | 28 | get length() { 29 | return this._streams.length + (this._running ? 1 : 0); 30 | } 31 | 32 | constructor(...streams: (Readable | StreamQueueFunction)[]); 33 | constructor( 34 | options: Partial, 35 | ...streams: (Readable | StreamQueueFunction)[] 36 | ); 37 | /** 38 | * Create a new queue and pipe given streams and end if some 39 | * @param options {Object} The queue options 40 | * @param options.objectMode {boolean} Operate in object mode 41 | * @param options.pauseFlowingStream {boolean} Pause given streams that are flowing 42 | * @param options.resumeFlowingStream {boolean} Resume given streams that are flowing 43 | * @param streams {...(Readable|Function)} The stream or stream returning function to pipe in 44 | * @returns StreamQueue 45 | */ 46 | constructor( 47 | maybeOptions: Partial | Readable | StreamQueueFunction, 48 | ...restStreams: (Readable | StreamQueueFunction)[] 49 | ) { 50 | const options = { 51 | ...DEFAULT_OPTIONS, 52 | ...(maybeOptions instanceof Stream || 'function' === typeof maybeOptions 53 | ? {} 54 | : maybeOptions || {}), 55 | }; 56 | const streams = 57 | maybeOptions instanceof Stream || 'function' === typeof maybeOptions 58 | ? [maybeOptions, ...restStreams] 59 | : restStreams; 60 | const superOptions: Partial = { ...options }; 61 | 62 | delete superOptions.pauseFlowingStream; 63 | delete superOptions.resumeFlowingStream; 64 | 65 | super(superOptions); 66 | 67 | this._options = options; 68 | 69 | // Prepare the stream to pipe in 70 | this._internalStream = new Writable(options); 71 | this._internalStream._write = (chunk, encoding, cb) => { 72 | if (this.push(chunk)) { 73 | cb(); 74 | return true; 75 | } 76 | this._awaitDrain = cb; 77 | return false; 78 | }; 79 | 80 | if (streams.length) { 81 | this.done(...streams); 82 | } 83 | } 84 | 85 | /** 86 | * Queue each stream given in argument 87 | * @param streams {Readable|Function} The stream or stream returning function to pipe in 88 | * @returns StreamQueue 89 | */ 90 | queue(...streams: (Readable | StreamQueueFunction)[]) { 91 | if (this._ending) { 92 | throw new YError('E_QUEUE_ALREADY_ENDED'); 93 | } 94 | 95 | for (const maybeStream of streams) { 96 | const stream = 97 | 'function' === typeof maybeStream ? maybeStream() : maybeStream; 98 | const wrapper = (stream) => { 99 | stream.on('error', (err) => { 100 | this.emit('error', err); 101 | }); 102 | if ('undefined' == typeof stream._readableState) { 103 | stream = new Stream.Readable({ 104 | objectMode: this._options.objectMode, 105 | }).wrap(stream); 106 | } 107 | if (this._options.pauseFlowingStream && stream._readableState.flowing) { 108 | stream.pause(); 109 | } 110 | return stream; 111 | }; 112 | 113 | this._streams.push(wrapper(stream)); 114 | } 115 | 116 | if (!this._running) { 117 | this._pipeNextStream(); 118 | } 119 | 120 | return this; 121 | } 122 | 123 | _read() { 124 | if (this._awaitDrain) { 125 | this._awaitDrain(); 126 | this._awaitDrain = null; 127 | this._internalStream.emit('drain'); 128 | } 129 | } 130 | 131 | private _pipeNextStream() { 132 | const nextStream = this._streams.shift(); 133 | 134 | if (!nextStream) { 135 | if (this._ending) { 136 | this.push(null); 137 | } else { 138 | this._running = false; 139 | } 140 | return; 141 | } 142 | this._running = true; 143 | 144 | if ('function' === typeof nextStream) { 145 | this._curStream = (nextStream as StreamQueueFunction)(); 146 | } else { 147 | this._curStream = nextStream; 148 | } 149 | 150 | (this._curStream as Readable).once('end', () => { 151 | this._pipeNextStream(); 152 | }); 153 | if ( 154 | this._options.resumeFlowingStream && 155 | (this._curStream as Readable).readableFlowing 156 | ) { 157 | (this._curStream as Readable).resume(); 158 | } 159 | (this._curStream as Readable).pipe(this._internalStream, { 160 | end: false, 161 | }); 162 | } 163 | 164 | /** 165 | * Queue each stream given in argument and end 166 | * @param streams {Readable|Function} The stream or stream returning function to pipe in 167 | * @returns StreamQueue 168 | */ 169 | done(...streams: (Readable | StreamQueueFunction)[]) { 170 | if (this._ending) { 171 | throw new YError('E_QUEUE_ALREADY_ENDED'); 172 | } 173 | if (streams.length) { 174 | this.queue(...streams); 175 | } 176 | 177 | this._ending = true; 178 | if (!this._running) { 179 | this.push(null); 180 | } 181 | return this; 182 | } 183 | } 184 | 185 | export function queueObjectStreams( 186 | ...streams: (Readable | StreamQueueFunction)[] 187 | ); 188 | export function queueObjectStreams( 189 | options: Partial>, 190 | ...streams: (Readable | StreamQueueFunction)[] 191 | ); 192 | /** 193 | * Create a new queue in object mode and pipe given streams and end if some 194 | * @param options {Object} The queue options 195 | * @param streams {...(Readable|Function)} The stream or stream returning function to pipe in 196 | * @returns StreamQueue 197 | */ 198 | export function queueObjectStreams( 199 | maybeOptions: 200 | | Partial> 201 | | Readable 202 | | StreamQueueFunction, 203 | ...restStreams: (Readable | StreamQueueFunction)[] 204 | ) { 205 | const options = 206 | maybeOptions instanceof Stream || 'function' === typeof maybeOptions 207 | ? {} 208 | : maybeOptions || {}; 209 | const streams = 210 | maybeOptions instanceof Stream || 'function' === typeof maybeOptions 211 | ? [maybeOptions, ...restStreams] 212 | : restStreams; 213 | 214 | return new StreamQueue( 215 | { 216 | ...options, 217 | objectMode: true, 218 | }, 219 | ...streams, 220 | ); 221 | } 222 | 223 | export function queueStreams(...streams: (Readable | StreamQueueFunction)[]); 224 | export function queueStreams( 225 | options: Partial, 226 | ...streams: (Readable | StreamQueueFunction)[] 227 | ); 228 | /** 229 | * Create a new queue and pipe given streams and end if some 230 | * @param options {Object} The queue options 231 | * @param streams {...(Readable|Function)} The stream or stream returning function to pipe in 232 | * @returns StreamQueue 233 | */ 234 | export function queueStreams( 235 | maybeOptions: Partial | Readable | StreamQueueFunction, 236 | ...restStreams: (Readable | StreamQueueFunction)[] 237 | ) { 238 | const options = 239 | maybeOptions instanceof Stream || 'function' === typeof maybeOptions 240 | ? {} 241 | : maybeOptions || {}; 242 | const streams = 243 | maybeOptions instanceof Stream || 'function' === typeof maybeOptions 244 | ? [maybeOptions, ...restStreams] 245 | : restStreams; 246 | return new StreamQueue(options, ...streams); 247 | } 248 | 249 | export { StreamQueue }; 250 | -------------------------------------------------------------------------------- /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 | } --------------------------------------------------------------------------------