├── .npmrc ├── .npmignore ├── .gitignore ├── spec ├── index.ts ├── tsconfig.json ├── writable.ts └── readable.ts ├── docs └── logo.png ├── .travis.yml ├── src ├── tsconfig.json ├── index.ts └── corsa.ts ├── package.json ├── tasks.js ├── license └── readme.md /.npmrc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | test/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | public/ -------------------------------------------------------------------------------- /spec/index.ts: -------------------------------------------------------------------------------- 1 | import './writable' 2 | import './readable' 3 | -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinclairzx81/corsa/HEAD/docs/logo.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | before_script: 5 | - npm install -------------------------------------------------------------------------------- /spec/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "esnext", 5 | "moduleResolution": "node", 6 | "removeComments": true, 7 | "lib": ["esnext", "dom"] 8 | }, 9 | "files": ["index.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "removeComments": false, 7 | "declaration": true, 8 | "lib": ["esnext", "dom"] 9 | }, 10 | "files": ["index.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "corsa", 3 | "version": "1.0.2", 4 | "description": "Async iteration channels in JavaScript.", 5 | "author": "sinclairzx81", 6 | "license": "MIT", 7 | "keywords": ["async-iterators", "channels", "streams", "backpressure"], 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/sinclairzx81/corsa" 11 | }, 12 | "scripts": { 13 | "clean": "smoke-task clean", 14 | "start": "smoke-task start", 15 | "test": "smoke-task test", 16 | "pack": "smoke-task pack" 17 | }, 18 | "main": "index.js", 19 | "devDependencies": { 20 | "@types/chai": "^4.1.3", 21 | "@types/mocha": "^5.2.0", 22 | "@types/node": "^8.0.7", 23 | "chai": "^4.1.2", 24 | "mocha": "^5.2.0", 25 | "shx": "^0.2.2", 26 | "smoke-run": "^1.0.2", 27 | "smoke-task": "^1.0.5", 28 | "typescript": "^3.2.1", 29 | "typescript-bundle": "^1.0.11" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tasks.js: -------------------------------------------------------------------------------- 1 | export async function clean() { 2 | await shell('shx rm -rf public') 3 | await shell('shx rm -rf node_modules') 4 | } 5 | 6 | export async function start() { 7 | await shell('tsc-bundle src/tsconfig.json --outFile public/bin/index.js') 8 | await Promise.all([ 9 | shell('tsc-bundle ./src/tsconfig.json --outFile public/bin/index.js --watch > /dev/null'), 10 | shell('smoke-run public/bin -- node public/bin/index.js') 11 | ]) 12 | } 13 | 14 | export async function test() { 15 | await shell('tsc-bundle ./spec/tsconfig.json --outFile ./public/spec/index.js') 16 | await shell('mocha public/spec/index.js') 17 | } 18 | 19 | export async function pack() { 20 | await shell('shx rm -rf ./public/pack') 21 | await shell('shx mkdir -p ./public/pack') 22 | await shell('tsc --project ./src/tsconfig.json --outDir ./public/pack') 23 | await shell('shx cp package.json public/pack') 24 | await shell('shx cp readme.md public/pack') 25 | await shell('shx cp license public/pack') 26 | await shell('cd public/pack && npm pack') 27 | } 28 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) corsa 2019 Haydn Paterson (sinclair) 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 all 13 | 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 THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /*-------------------------------------------------------------------------- 2 | 3 | MIT License 4 | 5 | Copyright (c) corsa 2019 Haydn Paterson (sinclair) 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | 25 | ---------------------------------------------------------------------------*/ 26 | 27 | export { IReadable, Readable } from './corsa' 28 | export { IWritable, Writable } from './corsa' 29 | export { IChannel, channel } from './corsa' 30 | export { select } from './corsa' 31 | -------------------------------------------------------------------------------- /spec/writable.ts: -------------------------------------------------------------------------------- 1 | import { channel } from '../src' 2 | 3 | describe('Writable', () => { 4 | it('should send values (unbounded, no-await)', async () => { 5 | const { writable } = channel() 6 | writable.write(1) 7 | writable.write(2) 8 | writable.write(3) 9 | }) 10 | it('should send values (unbounded, await)', async () => { 11 | const { writable } = channel() 12 | await writable.write(1) 13 | await writable.write(2) 14 | await writable.write(3) 15 | }) 16 | it('should send values (bounded, no-await)', async () => { 17 | const { writable } = channel(3) 18 | writable.write(1) 19 | writable.write(2) 20 | writable.write(3) 21 | }) 22 | it('should send values (bounded, await)', async () => { 23 | const { writable } = channel(3) 24 | await writable.write(1) 25 | await writable.write(2) 26 | await writable.write(3) 27 | }) 28 | it('should defer (bounded, await, expect timeout)', () => { 29 | return new Promise(async (resolve, reject) => { 30 | const { writable } = channel(3) 31 | await writable.write(1) 32 | await writable.write(2) 33 | await writable.write(3) 34 | setTimeout(() => resolve(), 250) 35 | writable.write(4).then(() => reject()) 36 | }) 37 | }) 38 | it('should defer and resume (bounded, await)', () => { 39 | return new Promise(async (resolve, reject) => { 40 | const { writable, readable } = channel(3) 41 | await writable.write(1) 42 | await writable.write(2) 43 | await writable.write(3) 44 | 45 | writable.write(4).then(() => resolve()) 46 | setTimeout(() => readable.read(), 100) // resolve first 47 | setTimeout(() => reject(), 200) // resolve last 48 | }) 49 | }) 50 | }) -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Corsa 2 | 3 | Async iteration channels in JavaScript. 4 | 5 | [![NPM package](https://badge.fury.io/js/corsa.svg)](https://www.npmjs.com/package/corsa) 6 | [![Build Status](https://travis-ci.org/sinclairzx81/corsa.svg?branch=master)](https://travis-ci.org/sinclairzx81/corsa) 7 | 8 | ``` 9 | $ npm install corsa --save 10 | ``` 11 | ```typescript 12 | import { channel } from 'corsa' 13 | 14 | const { readable, writable } = channel() 15 | writable.write(3) 16 | writable.write(2) 17 | writable.write(1) 18 | writable.end() 19 | 20 | for await (const value of readable) { 21 | console.log(value) 22 | } 23 | console.log('done') 24 | ``` 25 | ``` 26 | output: 27 | > 3 28 | > 2 29 | > 1 30 | > done 31 | ``` 32 | 33 | ## Overview 34 | 35 | Corsa is a library for creating buffered readable / writable channels in JavaScript. This library was specifically written to help solve backpressure issues that can occur when dealing with high frequency messaging using traditional event listeners in JavaScript. 36 | 37 | Corsa approaches this problem by making the channels sender await and suspend at buffer capacity. This helps to ensure the senders send rate is locked to the throughput allowed by a receiver. 38 | 39 | Requires async/await and AsyncIteration support. Tested natively on Node v10. 40 | 41 | ## channel<T> 42 | 43 | A channel is a uni-directional pipe for which data can flow. The following code creates an `unbounded` channel which allows for near infinite buffering of messages between `writable` and `readable`. The call to channel returns a `channel` object, which we destructure into the readable and writable pairs. 44 | 45 | ```typescript 46 | const { readable, writable } = channel() 47 | ``` 48 | The following creates a bounded channel which allows for sending `5` values before suspending (see bounded vs unbounded) 49 | 50 | ```typescript 51 | const { readable, writable } = channel(5) 52 | ``` 53 | 54 | ## Writer<T> 55 | 56 | The following code creates an unbounded channel and sends the values `1, 2, 3` followed by a call to `end()` to signal `EOF` to a receiver. 57 | 58 | ```typescript 59 | const { readable, writable } = channel() 60 | 61 | writable.write(1) 62 | writable.write(2) 63 | writable.write(3) 64 | writable.end() 65 | 66 | ``` 67 | 68 | ## Reader<T> 69 | 70 | The `Reader` is the receiving side of a channel and supports `for-await-of` for general iteration. 71 | 72 | ```typescript 73 | const { readable, writable } = channel() 74 | writable.write(1) 75 | writable.write(2) 76 | writable.write(3) 77 | writable.end() 78 | 79 | for await (const value of readable) { 80 | console.log(value) 81 | } 82 | 83 | ``` 84 | 85 | ## bounded vs unbounded 86 | 87 | By default all channels are `unbounded` but it is possible to set a fixed buffering size when creating a `channel()`. When setting a channel size, this will cause a writable to pause at `await` when sending values. The `await` at the writable will only occur once the channels buffer has filled with values. The writable will remained suspended until such time a receiver starts pulling values from the channel. 88 | 89 | The following code demostrates this behavior with channel bound to a buffer of 5. 90 | 91 | ```typescript 92 | const { readable, writable } = channel(5) 93 | await writable.write(1) 94 | await writable.write(2) 95 | await writable.write(3) 96 | await writable.write(4) 97 | await writable.write(5) // - at capacity, the readable will need to read something. 98 | 99 | await writable.write(6) // suspend <-----+ 100 | // | - readable.read() dequeues one element from the 101 | ... // | stream which will cause the writable to resume. 102 | // | 103 | await readable.read() // resume ------+ 104 | ``` 105 | 106 | ## select 107 | 108 | This library provides a simple channel `select` function similar to multi channel select found in the Go programming language. It allows multiple `Reader` types to be combined into a singular stream. 109 | 110 | ```typescript 111 | import { channel, select } from 'corsa' 112 | 113 | function strings() { 114 | const { readable, writable } = channel() 115 | setInterval(() => writable.write('hello world'), 100) 116 | return readable 117 | } 118 | 119 | function numbers() { 120 | const { readable, writable } = channel() 121 | setInterval(() => writable.write(Math.random()), 200) 122 | return readable 123 | } 124 | 125 | async function start() { 126 | const readable = select(strings(), numbers()) 127 | for await (const value of readable) { 128 | console.log(value) 129 | } 130 | } 131 | ``` 132 | -------------------------------------------------------------------------------- /spec/readable.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import { channel } from '../src' 3 | 4 | const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)) 5 | const move = (func: Function) => func() 6 | 7 | describe('Readable', () => { 8 | it('should recv values (unbounded, manual)', async () => { 9 | const { writable, readable } = channel() 10 | writable.write(1) 11 | writable.write(2) 12 | writable.write(3) 13 | writable.end() 14 | const result = await Promise.all([ 15 | readable.read(), 16 | readable.read(), 17 | readable.read(), 18 | readable.read() 19 | ]) 20 | expect(result).to.be.deep.eq([1, 2, 3, undefined]) 21 | }) 22 | 23 | it('should recv values (unbounded, for-await-of)', async () => { 24 | const { writable, readable } = channel() 25 | writable.write(1) 26 | writable.write(2) 27 | writable.write(3) 28 | writable.end() 29 | 30 | const result = [] 31 | for await (const value of readable) { 32 | result.push(value) 33 | } 34 | expect(result).to.be.deep.eq([1, 2, 3]) 35 | }) 36 | 37 | it('should recv values (unbounded, for-await-of, read-first)', async () => { 38 | return new Promise((resolve, _) => { 39 | const { writable, readable } = channel() 40 | // read 41 | move(async() => { 42 | const result = [] 43 | for await (const value of readable) { 44 | result.push(value) 45 | } 46 | expect(result).to.be.deep.eq([1, 2, 3]) 47 | resolve() 48 | }) 49 | // write 50 | move(async () => { 51 | writable.write(1) 52 | writable.write(2) 53 | writable.write(3) 54 | writable.end() 55 | }) 56 | }) 57 | }) 58 | 59 | it('should recv values (unbounded, for-await-of, write-first)', async () => { 60 | return new Promise((resolve, _) => { 61 | const { writable, readable } = channel() 62 | // write 63 | move(async () => { 64 | writable.write(1) 65 | writable.write(2) 66 | writable.write(3) 67 | writable.end() 68 | }) 69 | 70 | // read 71 | move(async() => { 72 | const result = [] 73 | for await (const value of readable) { 74 | result.push(value) 75 | } 76 | expect(result).to.be.deep.eq([1, 2, 3]) 77 | resolve() 78 | }) 79 | }) 80 | }) 81 | 82 | it('should recv values (bounded, manual)', async () => { 83 | const { writable, readable } = channel(4) 84 | await writable.write(1) 85 | await writable.write(2) 86 | await writable.write(3) 87 | await writable.end() 88 | 89 | const result = await Promise.all([ 90 | readable.read(), 91 | readable.read(), 92 | readable.read(), 93 | readable.read(), 94 | ]) 95 | expect(result).to.be.deep.eq([1, 2, 3, undefined]) 96 | }) 97 | 98 | it('should recv values (bounded, for-await-of)', async () => { 99 | const { writable, readable } = channel(4) 100 | await writable.write(1) 101 | await writable.write(2) 102 | await writable.write(3) 103 | await writable.end() 104 | 105 | const result = [] 106 | for await (const value of readable) { 107 | result.push(value) 108 | } 109 | expect(result).to.be.deep.eq([1, 2, 3]) 110 | }) 111 | 112 | it('should recv values (bounded, for-await-of, read-first)', async () => { 113 | return new Promise((resolve, _) => { 114 | const { writable, readable } = channel(4) 115 | // read 116 | move(async() => { 117 | const result = [] 118 | for await (const value of readable) { 119 | result.push(value) 120 | } 121 | expect(result).to.be.deep.eq([1, 2, 3]) 122 | resolve() 123 | }) 124 | // write 125 | move(async () => { 126 | await writable.write(1) 127 | await writable.write(2) 128 | await writable.write(3) 129 | await writable.end() 130 | }) 131 | }) 132 | }) 133 | 134 | it('should recv values (bounded, for-await-of, write-first)', async () => { 135 | return new Promise((resolve, _) => { 136 | const { writable, readable } = channel(4) 137 | // write 138 | move(async () => { 139 | await writable.write(1) 140 | await writable.write(2) 141 | await writable.write(3) 142 | await writable.end() 143 | }) 144 | 145 | // read 146 | move(async() => { 147 | const result = [] 148 | for await (const value of readable) { 149 | result.push(value) 150 | } 151 | expect(result).to.be.deep.eq([1, 2, 3]) 152 | resolve() 153 | }) 154 | }) 155 | }) 156 | 157 | it('should track state during iteration (bounded(1), manual)', async () => { 158 | return new Promise(async (resolve, reject) => { 159 | const { writable, readable } = channel(1) 160 | let state = 0 161 | move(async () => { 162 | await writable.write(1) 163 | state = 1 164 | await writable.write(2) 165 | state = 2 166 | await writable.write(3) 167 | state = 3 168 | }) 169 | 170 | // defer to allow move entry. 171 | setTimeout(async () => { 172 | expect(state).to.be.eq(1) 173 | await readable.read() // read: 1 174 | await delay(1) 175 | expect(state).to.be.eq(2) 176 | await readable.read() // read: 2 177 | await delay(1) 178 | expect(state).to.be.eq(3) 179 | await readable.read() // read: 3 180 | await delay(1) 181 | resolve() 182 | }) 183 | }) 184 | }) 185 | }) -------------------------------------------------------------------------------- /src/corsa.ts: -------------------------------------------------------------------------------- 1 | /*-------------------------------------------------------------------------- 2 | 3 | MIT License 4 | 5 | Copyright (c) corsa 2019 Haydn Paterson (sinclair) 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | 25 | ---------------------------------------------------------------------------*/ 26 | 27 | export interface IReadable { 28 | [Symbol.iterator]() 29 | [Symbol.asyncIterator]() 30 | read(): Promise 31 | } 32 | 33 | export class ReadableIterator implements Iterator> { 34 | constructor(private readonly readable: IReadable) { } 35 | public next(): IteratorResult> { 36 | const value = this.readable.read() 37 | const done = false 38 | return { value, done } 39 | } 40 | } 41 | 42 | export class ReadableAsyncIterator implements AsyncIterator { 43 | constructor(private readonly readable: IReadable) { } 44 | public async next(): Promise> { 45 | const next = await this.readable.read() 46 | if (next === undefined) { 47 | const done = true 48 | const value = null 49 | return { done, value } 50 | } 51 | const done = false 52 | const value = next 53 | return { done, value } 54 | } 55 | } 56 | 57 | export class Readable implements IReadable { 58 | [Symbol.iterator]() { return new ReadableIterator(this) } 59 | [Symbol.asyncIterator]() { return new ReadableAsyncIterator(this) } 60 | constructor(private readonly reader: IReadable) { } 61 | /** Reads the next value from this channel or `undefined` if eof. */ 62 | public read(): Promise { 63 | return this.reader.read() 64 | } 65 | } 66 | 67 | 68 | 69 | export interface IWritable { 70 | /** Writes data to the channel. */ 71 | write(data: T): Promise 72 | 73 | /** Ends this channel. */ 74 | end(): Promise 75 | } 76 | 77 | export class Writable implements IWritable { 78 | constructor(private writer: IWritable) { 79 | } 80 | /** Writes data to the channel. */ 81 | public write(data: T): Promise { 82 | return this.writer.write(data) 83 | } 84 | 85 | /** Ends this channel. */ 86 | public end(): Promise { 87 | return this.writer.end() 88 | } 89 | } 90 | 91 | 92 | 93 | interface Defer { 94 | resolve: (value: T) => void 95 | reject: (error: Error) => void 96 | } 97 | 98 | /** 99 | * Stream 100 | * 101 | * An in-memory asynchronous stream of values. Implements both 102 | * IReadable and IWritable interfaces and is used as a back 103 | * plane for in memory channels. 104 | */ 105 | export class Stream implements IReadable, IWritable { 106 | [Symbol.iterator]() { return new ReadableIterator(this) } 107 | [Symbol.asyncIterator]() { return new ReadableAsyncIterator(this) } 108 | 109 | private writers: Array = [] 110 | private sinks: Array> = [] 111 | private queue: T[] = [] 112 | 113 | constructor(private bounds: number = 1) {} 114 | 115 | /** Writes data to the channel. */ 116 | public async write (value: T): Promise { 117 | if (this.queue.length >= this.bounds) { 118 | await this.writePause() 119 | this.readResume(value) 120 | return 121 | } 122 | this.readResume(value) 123 | } 124 | 125 | /** Ends this stream. */ 126 | public async end (): Promise { 127 | if (this.queue.length >= this.bounds) { 128 | await this.writePause() 129 | this.readResume(void 0) 130 | } else { 131 | this.readResume(void 0) 132 | } 133 | } 134 | 135 | /** Reads the next value from this channel or `undefined` if eof. */ 136 | public read (): Promise { 137 | this.writeResume() 138 | if (this.queue.length > 0) { 139 | return Promise.resolve(this.queue.shift()!) 140 | } else { 141 | return this.readPause() 142 | } 143 | } 144 | 145 | private writePause() { 146 | return new Promise((resolve, reject) => 147 | this.writers.push({ resolve, reject }) 148 | ) 149 | } 150 | 151 | private writeResume() { 152 | if (this.writers.length > 0) { 153 | const writer = this.writers.shift()! 154 | writer.resolve(void 0) 155 | } 156 | } 157 | 158 | private readPause() { 159 | return new Promise((resolve, reject) => { 160 | this.sinks.push({ resolve, reject }) 161 | }) 162 | } 163 | 164 | private readResume (value: T) { 165 | if (this.sinks.length > 0) { 166 | const sink = this.sinks.shift()! 167 | return sink.resolve(value) 168 | } 169 | this.queue.push(value) 170 | } 171 | } 172 | 173 | 174 | /** 175 | * Selects from the given IReadable types and produces a 176 | * new multiplexed IReadable merging elements for each. 177 | */ 178 | export function select( 179 | r1: IReadable, 180 | r2: IReadable, 181 | r3: IReadable, 182 | r4: IReadable, 183 | r5: IReadable, 184 | r6: IReadable, 185 | r7: IReadable, 186 | r8: IReadable 187 | ): IReadable 188 | 189 | /** 190 | * Selects from the given IReadable types and produces a 191 | * new multiplexed IReadable merging elements for each. 192 | */ 193 | export function select( 194 | r1: IReadable, 195 | r2: IReadable, 196 | r3: IReadable, 197 | r4: IReadable, 198 | r5: IReadable, 199 | r6: IReadable, 200 | r7: IReadable 201 | ): IReadable 202 | 203 | /** 204 | * Selects from the given IReadable types and produces a 205 | * new multiplexed IReadable merging elements for each. 206 | */ 207 | export function select( 208 | r1: IReadable, 209 | r2: IReadable, 210 | r3: IReadable, 211 | r4: IReadable, 212 | r5: IReadable, 213 | r6: IReadable 214 | ): IReadable 215 | 216 | /** 217 | * Selects from the given IReadable types and produces a 218 | * new multiplexed IReadable merging elements for each. 219 | */ 220 | export function select( 221 | r1: IReadable, 222 | r2: IReadable, 223 | r3: IReadable, 224 | r4: IReadable, 225 | r5: IReadable 226 | ): IReadable 227 | 228 | /** 229 | * Selects from the given IReadable types and produces a 230 | * new multiplexed IReadable merging elements for each. 231 | */ 232 | export function select( 233 | r1: IReadable, 234 | r2: IReadable, 235 | r3: IReadable, 236 | r4: IReadable 237 | ): IReadable 238 | 239 | /** 240 | * Selects from the given IReadable types and produces a 241 | * new multiplexed IReadable merging elements for each. 242 | */ 243 | export function select( 244 | r1: IReadable, 245 | r2: IReadable, 246 | r3: IReadable 247 | ): IReadable 248 | 249 | /** 250 | * Selects from the given IReadable types and produces a multiplexed 251 | * IReadable combining elements for each. 252 | */ 253 | export function select( 254 | r1: IReadable, 255 | r2: IReadable 256 | ): IReadable 257 | 258 | /** 259 | * Selects from the given IReadable types and produces a 260 | * new multiplexed IReadable merging elements for each. 261 | */ 262 | export function select(r1: IReadable): IReadable 263 | 264 | /** 265 | * Selects from the given IReadable types and produces a 266 | * new multiplexed IReadable merging elements for each. 267 | */ 268 | export function select(...readers: Array>): IReadable { 269 | const { writable: writableHost, readable: readableHost } = channel(1) 270 | let completed = 0 271 | readers.forEach(async reader => { 272 | for await (const value of reader as any) { 273 | await writableHost.write(value) 274 | } 275 | completed += 1 276 | if(completed === readers.length) { 277 | await writableHost.end() 278 | } 279 | }) 280 | return readableHost 281 | } 282 | 283 | 284 | export interface IChannel { 285 | readable: IReadable 286 | writable: IWritable 287 | } 288 | /** Creates a channel with optional buffering bounds. (default is Number.MAX_SAFE_INTEGER) */ 289 | export function channel(bound: number = Number.MAX_SAFE_INTEGER): IChannel { 290 | const stream = new Stream(bound) 291 | const readable = new Readable(stream) 292 | const writable = new Writable(stream) 293 | return { readable, writable } 294 | } --------------------------------------------------------------------------------