├── .gitignore ├── .travis.yml ├── package.json ├── test ├── take.js ├── end.js ├── index.js └── abort.js ├── LICENSE ├── index.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | node_modules/* 3 | npm_debug.log 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.6 4 | - 0.8 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pull-pushable", 3 | "description": "pull-stream with a push interface", 4 | "version": "2.2.0", 5 | "homepage": "https://github.com/dominictarr/pull-pushable", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/dominictarr/pull-pushable.git" 9 | }, 10 | "devDependencies": { 11 | "pull-stream": "^3.0.1", 12 | "standard": "^7.1.2", 13 | "tape": "^4.6.0" 14 | }, 15 | "scripts": { 16 | "test": "standard && tape test/*.js" 17 | }, 18 | "author": "Dominic Tarr (http://dominictarr.com)", 19 | "license": "MIT" 20 | } 21 | -------------------------------------------------------------------------------- /test/take.js: -------------------------------------------------------------------------------- 1 | var pull = require('pull-stream') 2 | var pushable = require('../') 3 | var test = require('tape') 4 | 5 | test('on close callback', function (t) { 6 | var i = 0 7 | 8 | var p = pushable(function (err) { 9 | if (err) throw err 10 | console.log('ended', err) 11 | t.equal(i, 3) 12 | t.end() 13 | }) 14 | 15 | pull( 16 | p, 17 | pull.take(3), 18 | pull.drain(function (d) { 19 | console.log(d) 20 | t.equal(d, ++i) 21 | }, console.log.bind(console, 'end')) 22 | ) 23 | 24 | p.push(1) 25 | p.push(2) 26 | p.push(3) 27 | p.push(4) 28 | p.push(5) 29 | }) 30 | -------------------------------------------------------------------------------- /test/end.js: -------------------------------------------------------------------------------- 1 | // var pull = require('pull-stream') 2 | var pushable = require('../') 3 | var test = require('tape') 4 | 5 | test('pushable', function (t) { 6 | var buf = pushable() 7 | t.plan(10) 8 | 9 | t.equal('function', typeof buf) 10 | t.equal(2, buf.length) 11 | 12 | buf.push(1) 13 | buf.push(2) 14 | buf.push(3) 15 | buf.end() 16 | 17 | buf(null, function (end, data) { 18 | t.equal(data, 1) 19 | t.notOk(end) 20 | buf(null, function (end, data) { 21 | t.equal(data, 2) 22 | t.notOk(end) 23 | buf(null, function (end, data) { 24 | t.equal(data, 3) 25 | t.notOk(end) 26 | buf(null, function (end, data) { 27 | t.equal(data, undefined) 28 | t.ok(end) 29 | t.end() 30 | }) 31 | }) 32 | }) 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Dominic Tarr 2 | 3 | Permission is hereby granted, free of charge, 4 | to any person obtaining a copy of this software and 5 | associated documentation files (the "Software"), to 6 | deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, 8 | merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom 10 | the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 20 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var pull = require('pull-stream') 2 | var pushable = require('../') 3 | var test = require('tape') 4 | 5 | test('pushable', function (t) { 6 | var buf = pushable() 7 | 8 | // should be a read function! 9 | 10 | t.equal('function', typeof buf) 11 | t.equal(2, buf.length) 12 | 13 | buf.push(1) 14 | 15 | t.deepEqual(buf.buffer, [1]) 16 | 17 | pull( 18 | buf, 19 | pull.collect(function (end, array) { 20 | console.log(array) 21 | t.deepEqual(array, [1, 2, 3]) 22 | t.end() 23 | }) 24 | ) 25 | 26 | // SOMETIMES YOU NEED PUSH! 27 | 28 | buf.push(2) 29 | buf.push(3) 30 | buf.end() 31 | }) 32 | 33 | test('pushable with separated functions', function (t) { 34 | t.plan(4) 35 | 36 | var { push, end, source, buffer } = pushable(true) 37 | 38 | t.is(typeof source, 'function', 'is a function') 39 | t.is(source.length, 2, 'is a source stream') 40 | 41 | push(1) 42 | push(2) 43 | 44 | t.deepEqual(buffer, [1, 2]) 45 | 46 | pull( 47 | source, 48 | pull.collect(function (err, data) { 49 | if (err) throw err 50 | console.log(data) 51 | t.same(data, [1, 2, 3], 'got expected output') 52 | }) 53 | ) 54 | 55 | push(3) 56 | end() 57 | }) 58 | -------------------------------------------------------------------------------- /test/abort.js: -------------------------------------------------------------------------------- 1 | // var pull = require('pull-stream') 2 | var tape = require('tape') 3 | var Pushable = require('../') 4 | 5 | tape('abort after a read', function (t) { 6 | t.plan(2) 7 | var _err = new Error('test error') 8 | var p = Pushable(function (err) { 9 | console.log('on close') 10 | t.equal(err, _err) 11 | }) 12 | 13 | // manual read. 14 | p(null, function (err, data) { 15 | console.log('read cb') 16 | t.equal(err, _err) 17 | }) 18 | 19 | p(_err, function () { 20 | console.log('abort cb') 21 | t.end() 22 | }) 23 | }) 24 | 25 | tape('abort without a read', function (t) { 26 | t.plan(1) 27 | var _err = new Error('test error') 28 | var p = Pushable(function (err) { 29 | console.log('on close') 30 | t.equal(err, _err) 31 | }) 32 | 33 | p(_err, function () { 34 | console.log('abort cb') 35 | t.end() 36 | }) 37 | }) 38 | 39 | tape('abort without a read, with data', function (t) { 40 | t.plan(1) 41 | var _err = new Error('test error') 42 | var p = Pushable(function (err) { 43 | console.log('on close') 44 | t.equal(err, _err) 45 | }) 46 | 47 | p(_err, function () { 48 | console.log('abort cb') 49 | t.end() 50 | }) 51 | 52 | p.push(1) 53 | }) 54 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = pullPushable 2 | 3 | function pullPushable (separated, onClose) { 4 | if (typeof separated === 'function') { 5 | onClose = separated 6 | separated = false 7 | } 8 | 9 | // create a buffer for data 10 | // that have been pushed 11 | // but not yet pulled. 12 | var buffer = [] 13 | 14 | // a pushable is a source stream 15 | // (abort, cb) => cb(end, data) 16 | // 17 | // when pushable is pulled, 18 | // keep references to abort and cb 19 | // so we can call back after 20 | // .end(end) or .push(data) 21 | var abort, cb 22 | function read (_abort, _cb) { 23 | if (_abort) { 24 | abort = _abort 25 | // if there is already a cb waiting, abort it. 26 | if (cb) callback(abort) 27 | } 28 | cb = _cb 29 | drain() 30 | } 31 | 32 | var ended 33 | function end (end) { 34 | ended = ended || end || true 35 | // attempt to drain 36 | drain() 37 | } 38 | 39 | function push (data) { 40 | if (ended) return 41 | // if sink already waiting, 42 | // we can call back directly. 43 | if (cb) { 44 | callback(abort, data) 45 | return 46 | } 47 | // otherwise buffer data 48 | buffer.push(data) 49 | } 50 | 51 | // Return functions separated from source { push, end, source } 52 | if (separated) { 53 | return { push: push, end: end, source: read, buffer: buffer } 54 | } 55 | 56 | // Return normal 57 | read.push = push 58 | read.end = end 59 | read.buffer = buffer 60 | return read 61 | 62 | // `drain` calls back to (if any) waiting 63 | // sink with abort, end, or next data. 64 | function drain () { 65 | if (!cb) return 66 | 67 | if (abort) callback(abort) 68 | else if (!buffer.length && ended) callback(ended) 69 | else if (buffer.length) callback(null, buffer.shift()) 70 | } 71 | 72 | // `callback` calls back to waiting sink, 73 | // and removes references to sink cb. 74 | function callback (err, val) { 75 | var _cb = cb 76 | // if error and pushable passed onClose, call it 77 | // the first time this stream ends or errors. 78 | if (err && onClose) { 79 | var c = onClose 80 | onClose = null 81 | c(err === true ? null : err) 82 | } 83 | cb = null 84 | _cb(err, val) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pull-pushable 2 | 3 | A pull-stream source with a pushable interface. 4 | 5 | Use this when you really can't pull from your source. 6 | For example, often I like to have a "live" stream. 7 | This would read a series of data, first old data, 8 | but then stay open and read new data as it comes in. 9 | 10 | In that case, the new data needs to be queued up while the old data is read, 11 | and also, the rate things are pushed into the queue doesn't affect the rate of reads. 12 | 13 | If there is no realtime aspect to this stream, it's likely that you don't need pushable. 14 | Instead try just using `pull.values(array)`. 15 | 16 | ## Example 17 | 18 | ```js 19 | var Pushable = require('pull-pushable') 20 | var pull = require('pull-stream') 21 | var p = Pushable() 22 | 23 | pull(p, pull.drain(console.log)) 24 | 25 | p.push(1) 26 | p.end() 27 | ``` 28 | 29 | Also, can provide a listener for when the stream is closed. 30 | 31 | ```js 32 | var Pushable = require('pull-pushable') 33 | var pull = require('pull-stream') 34 | var p = Pushable(function (err) { 35 | console.log('stream closed!') 36 | }) 37 | 38 | //read 3 times then abort. 39 | pull(p, pull.take(3), pull.drain(console.log)) 40 | 41 | p.push(1) 42 | p.push(2) 43 | p.push(3) 44 | p.push(4) //stream will be aborted before this is output 45 | ``` 46 | 47 | When giving the stream away and you don't want the user to have the `push`/`end` functions, 48 | you can pass a `separated` option. It returns `{ push, end, source, buffer }`. 49 | 50 | ```js 51 | function createStream () { 52 | var p = Pushable(true) // optionally pass `onDone` after it 53 | 54 | somethingAsync((err, data) => { 55 | if (err) return p.end(err) 56 | p.push(data) 57 | }) 58 | 59 | return p.source 60 | } 61 | 62 | var stream = createStream() 63 | // stream.push === undefined 64 | ``` 65 | 66 | The current buffer array is exposed as `buffer` if you need to inspect or 67 | manipulate it. 68 | 69 | ## API 70 | 71 | ### `var p = Pushable([separated][, onClose])` 72 | 73 | Returns an new instance of Pushable. 74 | 75 | If `separated` is `true`, then `p.source` will be exposed, allowing just the 76 | source stream to be exposed without access to the rest of the Pushable API. 77 | 78 | `onClose`, if provided, will be called with `err`, which may be `true` (normal 79 | stream end) or an `Error` instance, indicating an error occurred in the 80 | pipeline. 81 | 82 | ### `p.push(buf)` 83 | 84 | Push a `Buffer`, `buf`, into the Pushable. 85 | 86 | ### `p.end([err])` 87 | 88 | Ends the source, with optional error `err`. 89 | 90 | ### `p.buffer` 91 | 92 | The current buffer of unread data that's been pushed to `p`. 93 | 94 | ## License 95 | 96 | MIT 97 | 98 | --------------------------------------------------------------------------------