├── LICENSE ├── bin └── cmd.js ├── church.md ├── example ├── basics │ ├── classic0.js │ ├── classic1.js │ ├── concat.js │ ├── consume0.js │ ├── consume1.js │ ├── consume2.js │ ├── lines.js │ ├── message.txt │ ├── read0.js │ ├── read1.js │ ├── read2.js │ ├── through.js │ ├── transform0.js │ ├── write0.js │ ├── writing0.js │ └── writing1.js ├── dnode │ ├── crazy │ │ ├── client.js │ │ └── server.js │ └── simple │ │ ├── client.js │ │ └── server.js ├── html-streams │ ├── browser.js │ ├── build.sh │ ├── package.json │ ├── render.js │ ├── server.js │ └── static │ │ ├── index.html │ │ ├── row.html │ │ └── style.css ├── roll_your_own_socketio │ ├── index.html │ ├── main.js │ └── server.js ├── scuttlebutt │ ├── client.js │ ├── model.js │ ├── package.json │ └── server.js └── writable.js ├── package.json └── readme.markdown /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Attribution 3.0 Unported 2 | 3 | License 4 | 5 | THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS 6 | PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR 7 | OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS 8 | LICENSE OR COPYRIGHT LAW IS PROHIBITED. 9 | 10 | BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE 11 | BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED 12 | TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN 13 | CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. 14 | 15 | 1. Definitions 16 | 17 | "Adaptation" means a work based upon the Work, or upon the Work and other 18 | pre-existing works, such as a translation, adaptation, derivative work, 19 | arrangement of music or other alterations of a literary or artistic work, or 20 | phonogram or performance and includes cinematographic adaptations or any other 21 | form in which the Work may be recast, transformed, or adapted including in any 22 | form recognizably derived from the original, except that a work that constitutes 23 | a Collection will not be considered an Adaptation for the purpose of this 24 | License. For the avoidance of doubt, where the Work is a musical work, 25 | performance or phonogram, the synchronization of the Work in timed-relation with 26 | a moving image ("synching") will be considered an Adaptation for the purpose of 27 | this License. "Collection" means a collection of literary or artistic works, 28 | such as encyclopedias and anthologies, or performances, phonograms or 29 | broadcasts, or other works or subject matter other than works listed in Section 30 | 1(f) below, which, by reason of the selection and arrangement of their contents, 31 | constitute intellectual creations, in which the Work is included in its entirety 32 | in unmodified form along with one or more other contributions, each constituting 33 | separate and independent works in themselves, which together are assembled into 34 | a collective whole. A work that constitutes a Collection will not be considered 35 | an Adaptation (as defined above) for the purposes of this License. "Distribute" 36 | means to make available to the public the original and copies of the Work or 37 | Adaptation, as appropriate, through sale or other transfer of ownership. 38 | "Licensor" means the individual, individuals, entity or entities that offer(s) 39 | the Work under the terms of this License. "Original Author" means, in the case 40 | of a literary or artistic work, the individual, individuals, entity or entities 41 | who created the Work or if no individual or entity can be identified, the 42 | publisher; and in addition (i) in the case of a performance the actors, singers, 43 | musicians, dancers, and other persons who act, sing, deliver, declaim, play in, 44 | interpret or otherwise perform literary or artistic works or expressions of 45 | folklore; (ii) in the case of a phonogram the producer being the person or legal 46 | entity who first fixes the sounds of a performance or other sounds; and, (iii) 47 | in the case of broadcasts, the organization that transmits the broadcast. "Work" 48 | means the literary and/or artistic work offered under the terms of this License 49 | including without limitation any production in the literary, scientific and 50 | artistic domain, whatever may be the mode or form of its expression including 51 | digital form, such as a book, pamphlet and other writing; a lecture, address, 52 | sermon or other work of the same nature; a dramatic or dramatico-musical work; a 53 | choreographic work or entertainment in dumb show; a musical composition with or 54 | without words; a cinematographic work to which are assimilated works expressed 55 | by a process analogous to cinematography; a work of drawing, painting, 56 | architecture, sculpture, engraving or lithography; a photographic work to which 57 | are assimilated works expressed by a process analogous to photography; a work of 58 | applied art; an illustration, map, plan, sketch or three-dimensional work 59 | relative to geography, topography, architecture or science; a performance; a 60 | broadcast; a phonogram; a compilation of data to the extent it is protected as a 61 | copyrightable work; or a work performed by a variety or circus performer to the 62 | extent it is not otherwise considered a literary or artistic work. "You" means 63 | an individual or entity exercising rights under this License who has not 64 | previously violated the terms of this License with respect to the Work, or who 65 | has received express permission from the Licensor to exercise rights under this 66 | License despite a previous violation. "Publicly Perform" means to perform public 67 | recitations of the Work and to communicate to the public those public 68 | recitations, by any means or process, including by wire or wireless means or 69 | public digital performances; to make available to the public Works in such a way 70 | that members of the public may access these Works from a place and at a place 71 | individually chosen by them; to perform the Work to the public by any means or 72 | process and the communication to the public of the performances of the Work, 73 | including by public digital performance; to broadcast and rebroadcast the Work 74 | by any means including signs, sounds or images. "Reproduce" means to make copies 75 | of the Work by any means including without limitation by sound or visual 76 | recordings and the right of fixation and reproducing fixations of the Work, 77 | including storage of a protected performance or phonogram in digital form or 78 | other electronic medium. 2. Fair Dealing Rights. Nothing in this License is 79 | intended to reduce, limit, or restrict any uses free from copyright or rights 80 | arising from limitations or exceptions that are provided for in connection with 81 | the copyright protection under copyright law or other applicable laws. 82 | 83 | 3. License Grant. Subject to the terms and conditions of this License, Licensor 84 | hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the 85 | duration of the applicable copyright) license to exercise the rights in the Work 86 | as stated below: 87 | 88 | to Reproduce the Work, to incorporate the Work into one or more Collections, and 89 | to Reproduce the Work as incorporated in the Collections; to create and 90 | Reproduce Adaptations provided that any such Adaptation, including any 91 | translation in any medium, takes reasonable steps to clearly label, demarcate or 92 | otherwise identify that changes were made to the original Work. For example, a 93 | translation could be marked "The original work was translated from English to 94 | Spanish," or a modification could indicate "The original work has been 95 | modified."; to Distribute and Publicly Perform the Work including as 96 | incorporated in Collections; and, to Distribute and Publicly Perform 97 | Adaptations. For the avoidance of doubt: 98 | 99 | Non-waivable Compulsory License Schemes. In those jurisdictions in which the 100 | right to collect royalties through any statutory or compulsory licensing scheme 101 | cannot be waived, the Licensor reserves the exclusive right to collect such 102 | royalties for any exercise by You of the rights granted under this License; 103 | Waivable Compulsory License Schemes. In those jurisdictions in which the right 104 | to collect royalties through any statutory or compulsory licensing scheme can be 105 | waived, the Licensor waives the exclusive right to collect such royalties for 106 | any exercise by You of the rights granted under this License; and, Voluntary 107 | License Schemes. The Licensor waives the right to collect royalties, whether 108 | individually or, in the event that the Licensor is a member of a collecting 109 | society that administers voluntary licensing schemes, via that society, from any 110 | exercise by You of the rights granted under this License. The above rights may 111 | be exercised in all media and formats whether now known or hereafter devised. 112 | The above rights include the right to make such modifications as are technically 113 | necessary to exercise the rights in other media and formats. Subject to Section 114 | 8(f), all rights not expressly granted by Licensor are hereby reserved. 115 | 116 | 4. Restrictions. The license granted in Section 3 above is expressly made 117 | subject to and limited by the following restrictions: 118 | 119 | You may Distribute or Publicly Perform the Work only under the terms of this 120 | License. You must include a copy of, or the Uniform Resource Identifier (URI) 121 | for, this License with every copy of the Work You Distribute or Publicly 122 | Perform. You may not offer or impose any terms on the Work that restrict the 123 | terms of this License or the ability of the recipient of the Work to exercise 124 | the rights granted to that recipient under the terms of the License. You may not 125 | sublicense the Work. You must keep intact all notices that refer to this License 126 | and to the disclaimer of warranties with every copy of the Work You Distribute 127 | or Publicly Perform. When You Distribute or Publicly Perform the Work, You may 128 | not impose any effective technological measures on the Work that restrict the 129 | ability of a recipient of the Work from You to exercise the rights granted to 130 | that recipient under the terms of the License. This Section 4(a) applies to the 131 | Work as incorporated in a Collection, but this does not require the Collection 132 | apart from the Work itself to be made subject to the terms of this License. If 133 | You create a Collection, upon notice from any Licensor You must, to the extent 134 | practicable, remove from the Collection any credit as required by Section 4(b), 135 | as requested. If You create an Adaptation, upon notice from any Licensor You 136 | must, to the extent practicable, remove from the Adaptation any credit as 137 | required by Section 4(b), as requested. If You Distribute, or Publicly Perform 138 | the Work or any Adaptations or Collections, You must, unless a request has been 139 | made pursuant to Section 4(a), keep intact all copyright notices for the Work 140 | and provide, reasonable to the medium or means You are utilizing: (i) the name 141 | of the Original Author (or pseudonym, if applicable) if supplied, and/or if the 142 | Original Author and/or Licensor designate another party or parties (e.g., a 143 | sponsor institute, publishing entity, journal) for attribution ("Attribution 144 | Parties") in Licensor's copyright notice, terms of service or by other 145 | reasonable means, the name of such party or parties; (ii) the title of the Work 146 | if supplied; (iii) to the extent reasonably practicable, the URI, if any, that 147 | Licensor specifies to be associated with the Work, unless such URI does not 148 | refer to the copyright notice or licensing information for the Work; and (iv) , 149 | consistent with Section 3(b), in the case of an Adaptation, a credit identifying 150 | the use of the Work in the Adaptation (e.g., "French translation of the Work by 151 | Original Author," or "Screenplay based on original Work by Original Author"). 152 | The credit required by this Section 4 (b) may be implemented in any reasonable 153 | manner; provided, however, that in the case of a Adaptation or Collection, at a 154 | minimum such credit will appear, if a credit for all contributing authors of the 155 | Adaptation or Collection appears, then as part of these credits and in a manner 156 | at least as prominent as the credits for the other contributing authors. For the 157 | avoidance of doubt, You may only use the credit required by this Section for the 158 | purpose of attribution in the manner set out above and, by exercising Your 159 | rights under this License, You may not implicitly or explicitly assert or imply 160 | any connection with, sponsorship or endorsement by the Original Author, Licensor 161 | and/or Attribution Parties, as appropriate, of You or Your use of the Work, 162 | without the separate, express prior written permission of the Original Author, 163 | Licensor and/or Attribution Parties. Except as otherwise agreed in writing by 164 | the Licensor or as may be otherwise permitted by applicable law, if You 165 | Reproduce, Distribute or Publicly Perform the Work either by itself or as part 166 | of any Adaptations or Collections, You must not distort, mutilate, modify or 167 | take other derogatory action in relation to the Work which would be prejudicial 168 | to the Original Author's honor or reputation. Licensor agrees that in those 169 | jurisdictions (e.g. Japan), in which any exercise of the right granted in 170 | Section 3(b) of this License (the right to make Adaptations) would be deemed to 171 | be a distortion, mutilation, modification or other derogatory action prejudicial 172 | to the Original Author's honor and reputation, the Licensor will waive or not 173 | assert, as appropriate, this Section, to the fullest extent permitted by the 174 | applicable national law, to enable You to reasonably exercise Your right under 175 | Section 3(b) of this License (right to make Adaptations) but not otherwise. 5. 176 | Representations, Warranties and Disclaimer 177 | 178 | UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS 179 | THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING 180 | THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT 181 | LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR 182 | PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, 183 | OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME 184 | JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH 185 | EXCLUSION MAY NOT APPLY TO YOU. 186 | 187 | 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN 188 | NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, 189 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS 190 | LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE 191 | POSSIBILITY OF SUCH DAMAGES. 192 | 193 | 7. Termination 194 | 195 | This License and the rights granted hereunder will terminate automatically upon 196 | any breach by You of the terms of this License. Individuals or entities who have 197 | received Adaptations or Collections from You under this License, however, will 198 | not have their licenses terminated provided such individuals or entities remain 199 | in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will 200 | survive any termination of this License. Subject to the above terms and 201 | conditions, the license granted here is perpetual (for the duration of the 202 | applicable copyright in the Work). Notwithstanding the above, Licensor reserves 203 | the right to release the Work under different license terms or to stop 204 | distributing the Work at any time; provided, however that any such election will 205 | not serve to withdraw this License (or any other license that has been, or is 206 | required to be, granted under the terms of this License), and this License will 207 | continue in full force and effect unless terminated as stated above. 8. 208 | Miscellaneous 209 | 210 | Each time You Distribute or Publicly Perform the Work or a Collection, the 211 | Licensor offers to the recipient a license to the Work on the same terms and 212 | conditions as the license granted to You under this License. Each time You 213 | Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a 214 | license to the original Work on the same terms and conditions as the license 215 | granted to You under this License. If any provision of this License is invalid 216 | or unenforceable under applicable law, it shall not affect the validity or 217 | enforceability of the remainder of the terms of this License, and without 218 | further action by the parties to this agreement, such provision shall be 219 | reformed to the minimum extent necessary to make such provision valid and 220 | enforceable. No term or provision of this License shall be deemed waived and no 221 | breach consented to unless such waiver or consent shall be in writing and signed 222 | by the party to be charged with such waiver or consent. This License constitutes 223 | the entire agreement between the parties with respect to the Work licensed here. 224 | There are no understandings, agreements or representations with respect to the 225 | Work not specified here. Licensor shall not be bound by any additional 226 | provisions that may appear in any communication from You. This License may not 227 | be modified without the mutual written agreement of the Licensor and You. The 228 | rights granted under, and the subject matter referenced, in this License were 229 | drafted utilizing the terminology of the Berne Convention for the Protection of 230 | Literary and Artistic Works (as amended on September 28, 1979), the Rome 231 | Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and 232 | Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on 233 | July 24, 1971). These rights and subject matter take effect in the relevant 234 | jurisdiction in which the License terms are sought to be enforced according to 235 | the corresponding provisions of the implementation of those treaty provisions in 236 | the applicable national law. If the standard suite of rights granted under 237 | applicable copyright law includes additional rights not granted under this 238 | License, such additional rights are deemed to be included in the License; this 239 | License is not intended to restrict the license of any rights under applicable 240 | law. Creative Commons Notice 241 | 242 | Creative Commons is not a party to this License, and makes no warranty 243 | whatsoever in connection with the Work. Creative Commons will not be liable to 244 | You or any party on any legal theory for any damages whatsoever, including 245 | without limitation any general, special, incidental or consequential damages 246 | arising in connection to this license. Notwithstanding the foregoing two (2) 247 | sentences, if Creative Commons has expressly identified itself as the Licensor 248 | hereunder, it shall have all rights and obligations of Licensor. 249 | 250 | Except for the limited purpose of indicating to the public that the Work is 251 | licensed under the CCPL, Creative Commons does not authorize the use by either 252 | party of the trademark "Creative Commons" or any related trademark or logo of 253 | Creative Commons without the prior written consent of Creative Commons. Any 254 | permitted use will be in compliance with Creative Commons' then-current 255 | trademark usage guidelines, as may be published on its website or otherwise made 256 | available upon request from time to time. For the avoidance of doubt, this 257 | trademark restriction does not form part of this License. 258 | 259 | Creative Commons may be contacted at http://creativecommons.org/. 260 | -------------------------------------------------------------------------------- /bin/cmd.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var fs = require('fs'); 4 | var pager = require('default-pager'); 5 | 6 | fs.createReadStream(__dirname + '/../readme.markdown').pipe(pager()); 7 | -------------------------------------------------------------------------------- /church.md: -------------------------------------------------------------------------------- 1 | ### Michael O. Church: archived articles 2 | 3 | - [What is spaghetti code? August 15, 2012.](https://web.archive.org/web/20150910093715/https://michaelochurch.wordpress.com/2012/08/15/what-is-spaghetti-code/) 4 | - [Functional programs rarely rot. December 6, 2012.](https://web.archive.org/web/20151221082937/https://michaelochurch.wordpress.com/2012/12/06/functional-programs-rarely-rot/) 5 | -------------------------------------------------------------------------------- /example/basics/classic0.js: -------------------------------------------------------------------------------- 1 | var Stream = require('stream'); 2 | var stream = new Stream; 3 | stream.readable = true; 4 | 5 | var c = 64; 6 | var iv = setInterval(function () { 7 | if (++c >= 75) { 8 | clearInterval(iv); 9 | stream.emit('end'); 10 | } 11 | else stream.emit('data', String.fromCharCode(c)); 12 | }, 100); 13 | 14 | stream.pipe(process.stdout); 15 | -------------------------------------------------------------------------------- /example/basics/classic1.js: -------------------------------------------------------------------------------- 1 | process.stdin.on('data', function (buf) { 2 | console.log(buf); 3 | }); 4 | process.stdin.on('end', function () { 5 | console.log('__END__'); 6 | }); 7 | -------------------------------------------------------------------------------- /example/basics/concat.js: -------------------------------------------------------------------------------- 1 | var concat = require('concat-stream'); 2 | process.stdin.pipe(concat(function (body) { 3 | console.dir(JSON.parse(body)); 4 | })); 5 | -------------------------------------------------------------------------------- /example/basics/consume0.js: -------------------------------------------------------------------------------- 1 | process.stdin.on('readable', function () { 2 | var buf = process.stdin.read(); 3 | console.dir(buf); 4 | }); 5 | -------------------------------------------------------------------------------- /example/basics/consume1.js: -------------------------------------------------------------------------------- 1 | process.stdin.on('readable', function () { 2 | var buf = process.stdin.read(3); 3 | console.dir(buf); 4 | }); 5 | -------------------------------------------------------------------------------- /example/basics/consume2.js: -------------------------------------------------------------------------------- 1 | process.stdin.on('readable', function () { 2 | var buf = process.stdin.read(3); 3 | console.dir(buf); 4 | process.stdin.read(0); 5 | }); 6 | -------------------------------------------------------------------------------- /example/basics/lines.js: -------------------------------------------------------------------------------- 1 | var offset = 0; 2 | 3 | process.stdin.on('readable', function () { 4 | var buf = process.stdin.read(); 5 | if (!buf) return; 6 | for (; offset < buf.length; offset++) { 7 | if (buf[offset] === 0x0a) { 8 | console.dir(buf.slice(0, offset).toString()); 9 | buf = buf.slice(offset + 1); 10 | offset = 0; 11 | process.stdin.unshift(buf); 12 | return; 13 | } 14 | } 15 | process.stdin.unshift(buf); 16 | }); 17 | -------------------------------------------------------------------------------- /example/basics/message.txt: -------------------------------------------------------------------------------- 1 | beep boop 2 | -------------------------------------------------------------------------------- /example/basics/read0.js: -------------------------------------------------------------------------------- 1 | var Readable = require('stream').Readable; 2 | 3 | var rs = Readable(); 4 | rs.push('beep '); 5 | rs.push('boop\n'); 6 | rs.push(null); 7 | 8 | rs.pipe(process.stdout); 9 | -------------------------------------------------------------------------------- /example/basics/read1.js: -------------------------------------------------------------------------------- 1 | var Readable = require('stream').Readable; 2 | var rs = Readable(); 3 | 4 | var c = 97; 5 | rs._read = function () { 6 | rs.push(String.fromCharCode(c++)); 7 | if (c > 'z'.charCodeAt(0)) rs.push(null); 8 | }; 9 | 10 | rs.pipe(process.stdout); 11 | -------------------------------------------------------------------------------- /example/basics/read2.js: -------------------------------------------------------------------------------- 1 | var Readable = require('stream').Readable; 2 | var rs = Readable(); 3 | 4 | var count = 0; 5 | var c = 97 - 1; 6 | 7 | rs._read = function () { 8 | count++; 9 | if (c >= 'z'.charCodeAt(0)) return rs.push(null); 10 | 11 | setTimeout(function () { 12 | rs.push(String.fromCharCode(++c)); 13 | }, 100); 14 | }; 15 | 16 | rs.pipe(process.stdout); 17 | 18 | process.on('exit', function () { 19 | console.error('\n_read() called ' + count + ' times'); 20 | }); 21 | process.stdout.on('error', process.exit); 22 | -------------------------------------------------------------------------------- /example/basics/through.js: -------------------------------------------------------------------------------- 1 | var through = require('through'); 2 | process.stdin.pipe(through(write, end)); 3 | 4 | function write (buf) { 5 | console.log(buf); 6 | } 7 | function end () { 8 | console.log('__END__'); 9 | } 10 | -------------------------------------------------------------------------------- /example/basics/transform0.js: -------------------------------------------------------------------------------- 1 | var Transform = require('stream').Transform; 2 | 3 | var ts = Transform(); 4 | ts._transform = function(chunk, enc, next) { 5 | var data = chunk.toString(); 6 | data = data.toUpperCase(); 7 | this.push(data); // send the transformed data on its way 8 | next(); 9 | } 10 | 11 | process.on('exit', function() { 12 | console.error('\nYou cut me off!'); 13 | }); 14 | process.stdout.on('error', process.exit); 15 | 16 | process.stdin.pipe(ts).pipe(process.stdout); 17 | -------------------------------------------------------------------------------- /example/basics/write0.js: -------------------------------------------------------------------------------- 1 | var Writable = require('stream').Writable; 2 | var ws = Writable({ objectMode: true }); 3 | ws._write = function (chunk, enc, next) { 4 | console.dir(chunk); 5 | next(); 6 | }; 7 | 8 | process.stdin.pipe(ws); 9 | -------------------------------------------------------------------------------- /example/basics/writing0.js: -------------------------------------------------------------------------------- 1 | process.stdout.write('beep '); 2 | -------------------------------------------------------------------------------- /example/basics/writing1.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var ws = fs.createWriteStream('message.txt'); 3 | 4 | ws.write('beep '); 5 | 6 | setTimeout(function () { 7 | ws.end('boop\n'); 8 | }, 1000); 9 | -------------------------------------------------------------------------------- /example/dnode/crazy/client.js: -------------------------------------------------------------------------------- 1 | var dnode = require('dnode'); 2 | var net = require('net'); 3 | 4 | var d = dnode(); 5 | d.on('remote', function (remote) { 6 | remote.transform('beep', function (cb) { 7 | cb(10, function (s) { 8 | console.log('beep:10 => ' + s); 9 | d.end(); 10 | }); 11 | }); 12 | }); 13 | 14 | var c = net.connect(5004); 15 | c.pipe(d).pipe(c); 16 | -------------------------------------------------------------------------------- /example/dnode/crazy/server.js: -------------------------------------------------------------------------------- 1 | var dnode = require('dnode'); 2 | var net = require('net'); 3 | 4 | var server = net.createServer(function (c) { 5 | var d = dnode({ 6 | transform : function (s, cb) { 7 | cb(function (n, fn) { 8 | var oo = Array(n+1).join('o'); 9 | fn(s.replace(/[aeiou]{2,}/, oo).toUpperCase()); 10 | }); 11 | } 12 | }); 13 | c.pipe(d).pipe(c); 14 | }); 15 | 16 | server.listen(5004); 17 | -------------------------------------------------------------------------------- /example/dnode/simple/client.js: -------------------------------------------------------------------------------- 1 | var dnode = require('dnode'); 2 | var net = require('net'); 3 | 4 | var d = dnode(); 5 | d.on('remote', function (remote) { 6 | remote.transform('beep', function (s) { 7 | console.log('beep => ' + s); 8 | d.end(); 9 | }); 10 | }); 11 | 12 | var c = net.connect(5004); 13 | c.pipe(d).pipe(c); 14 | -------------------------------------------------------------------------------- /example/dnode/simple/server.js: -------------------------------------------------------------------------------- 1 | var dnode = require('dnode'); 2 | var net = require('net'); 3 | 4 | var server = net.createServer(function (c) { 5 | var d = dnode({ 6 | transform : function (s, cb) { 7 | cb(s.replace(/[aeiou]{2,}/, 'oo').toUpperCase()) 8 | } 9 | }); 10 | c.pipe(d).pipe(c); 11 | }); 12 | 13 | server.listen(5004); 14 | -------------------------------------------------------------------------------- /example/html-streams/browser.js: -------------------------------------------------------------------------------- 1 | var through = require('through'); 2 | var render = require('./render'); 3 | 4 | var shoe = require('shoe'); 5 | var stream = shoe('/sock'); 6 | 7 | var rows = document.querySelector('#rows'); 8 | stream.pipe(render()).pipe(through(function (html) { 9 | rows.innerHTML += html; 10 | })); 11 | -------------------------------------------------------------------------------- /example/html-streams/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | browserify -t brfs browser.js > static/bundle.js 3 | -------------------------------------------------------------------------------- /example/html-streams/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "html-streams-example", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "hyperglue": "~1.0.0", 6 | "slice-file": "~0.2.1", 7 | "through": "~2.3.4", 8 | "ecstatic": "~4.1.4", 9 | "shoe": "~0.0.10", 10 | "browserify": "~2.14.2", 11 | "brfs": "~0.2.2", 12 | "hyperstream": "~0.1.4" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /example/html-streams/render.js: -------------------------------------------------------------------------------- 1 | var through = require('through'); 2 | var hyperglue = require('hyperglue'); 3 | var fs = require('fs'); 4 | var html = fs.readFileSync(__dirname + '/static/row.html'); 5 | 6 | module.exports = function () { 7 | return through(function (line) { 8 | try { var row = JSON.parse(line) } 9 | catch (err) { return this.emit('error', err) } 10 | 11 | this.queue(hyperglue(html, { 12 | '.who': row.who, 13 | '.message': row.message 14 | }).outerHTML); 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /example/html-streams/server.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var fs = require('fs'); 3 | var hyperstream = require('hyperstream'); 4 | var ecstatic = require('ecstatic')(__dirname + '/static'); 5 | 6 | var sliceFile = require('slice-file'); 7 | var sf = sliceFile(__dirname + '/data.txt'); 8 | 9 | var render = require('./render'); 10 | 11 | var server = http.createServer(function (req, res) { 12 | if (req.url === '/') { 13 | var hs = hyperstream({ 14 | '#rows': sf.slice(-5).pipe(render()) 15 | }); 16 | hs.pipe(res); 17 | fs.createReadStream(__dirname + '/static/index.html').pipe(hs); 18 | } 19 | else ecstatic(req, res) 20 | }); 21 | server.listen(8000); 22 | 23 | var shoe = require('shoe'); 24 | var sock = shoe(function (stream) { 25 | sf.follow(-1,0).pipe(stream); 26 | }); 27 | sock.install(server, '/sock'); 28 | -------------------------------------------------------------------------------- /example/html-streams/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

rows

7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/html-streams/static/row.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | -------------------------------------------------------------------------------- /example/html-streams/static/style.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | color: rgb(60,60,150); 3 | } 4 | 5 | .row { 6 | background-color: rgb(200,200,230); 7 | padding: 20px; 8 | width: 500px; 9 | border-radius: 5px; 10 | margin-bottom: 10px; 11 | } 12 | 13 | .row .who { 14 | color: rgb(60,60,150); 15 | } 16 | -------------------------------------------------------------------------------- /example/roll_your_own_socketio/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /example/roll_your_own_socketio/main.js: -------------------------------------------------------------------------------- 1 | var shoe = require('shoe'); 2 | var emitStream = require('emit-stream'); 3 | var JSONStream = require('JSONStream'); 4 | 5 | var parser = JSONStream.parse([true]); 6 | var stream = parser.pipe(shoe('/sock')).pipe(parser); 7 | var ev = emitStream(stream); 8 | 9 | ev.on('lower', function (msg) { 10 | var div = document.createElement('div'); 11 | div.textContent = msg.toLowerCase(); 12 | document.body.appendChild(div); 13 | }); 14 | 15 | ev.on('upper', function (msg) { 16 | var div = document.createElement('div'); 17 | div.textContent = msg.toUpperCase(); 18 | document.body.appendChild(div); 19 | }); 20 | -------------------------------------------------------------------------------- /example/roll_your_own_socketio/server.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var EventEmitter = require('events').EventEmitter; 3 | 4 | var server = http.createServer(require('ecstatic')(__dirname)); 5 | server.listen(8080); 6 | 7 | var shoe = require('shoe'); 8 | var emitStream = require('emit-stream'); 9 | var JSONStream = require('JSONStream'); 10 | 11 | var sock = shoe(function (stream) { 12 | var ev = new EventEmitter; 13 | emitStream(ev) 14 | .pipe(JSONStream.stringify()) 15 | .pipe(stream) 16 | ; 17 | 18 | var intervals = []; 19 | 20 | intervals.push(setInterval(function () { 21 | ev.emit('upper', 'abc'); 22 | }, 500)); 23 | 24 | intervals.push(setInterval(function () { 25 | ev.emit('lower', 'def'); 26 | }, 300)); 27 | 28 | stream.on('end', function () { 29 | intervals.forEach(clearInterval); 30 | }); 31 | 32 | }); 33 | sock.install(server, '/sock'); 34 | -------------------------------------------------------------------------------- /example/scuttlebutt/client.js: -------------------------------------------------------------------------------- 1 | var Model = require('scuttlebutt/model'); 2 | var net = require('net'); 3 | 4 | var m = new Model; 5 | var s = m.createStream(); 6 | 7 | s.pipe(net.connect(8888, 'localhost')).pipe(s); 8 | 9 | m.on('update', function cb (key) { 10 | // wait until we've gotten at least one count value from the network 11 | if (key !== 'count') return; 12 | m.removeListener('update', cb); 13 | 14 | setInterval(function () { 15 | m.set('count', Number(m.get('count')) + 1); 16 | }, 100); 17 | }); 18 | 19 | m.on('update', function (key, value) { 20 | console.log(key + ' = ' + value); 21 | }); 22 | -------------------------------------------------------------------------------- /example/scuttlebutt/model.js: -------------------------------------------------------------------------------- 1 | var Model = require('scuttlebutt/model'); 2 | var am = new Model; 3 | var as = am.createStream(); 4 | 5 | var bm = new Model; 6 | var bs = bm.createStream(); 7 | 8 | var cm = new Model; 9 | var cs = cm.createStream(); 10 | 11 | var dm = new Model; 12 | var ds = dm.createStream(); 13 | 14 | var em = new Model; 15 | var es = em.createStream(); 16 | 17 | as.pipe(bs).pipe(as); 18 | bs.pipe(cs).pipe(bs); 19 | bs.pipe(ds).pipe(bs); 20 | ds.pipe(es).pipe(ds); 21 | 22 | em.on('update', function (key, value, source) { 23 | console.log(key + ' => ' + value + ' from ' + source); 24 | }); 25 | 26 | am.set('x', 555); 27 | 28 | setTimeout(function () { 29 | console.log('em.x=' + em.get('x')); 30 | }, 100); 31 | -------------------------------------------------------------------------------- /example/scuttlebutt/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "stream-handbook-scuttlebutt-example", 3 | "private" : true, 4 | "version" : "0.0.0", 5 | "dependencies" : { 6 | "scuttlebutt" : "~3.3.2" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /example/scuttlebutt/server.js: -------------------------------------------------------------------------------- 1 | var Model = require('scuttlebutt/model'); 2 | var net = require('net'); 3 | 4 | var m = new Model; 5 | m.set('count', '0'); 6 | m.on('update', function (key, value) { 7 | console.log(key + ' = ' + m.get('count')); 8 | }); 9 | 10 | var server = net.createServer(function (stream) { 11 | stream.pipe(m.createStream()).pipe(stream); 12 | }); 13 | server.listen(8888); 14 | 15 | setInterval(function () { 16 | m.set('count', Number(m.get('count')) + 1); 17 | }, 320); 18 | -------------------------------------------------------------------------------- /example/writable.js: -------------------------------------------------------------------------------- 1 | var Stream = require('stream'); 2 | var s = new Stream; 3 | s.writable = true; 4 | 5 | var bytes = 0; 6 | 7 | s.write = function (buf) { 8 | bytes += buf.length; 9 | }; 10 | 11 | s.end = function (buf) { 12 | if (arguments.length) s.write(buf); 13 | 14 | s.writable = false; 15 | console.log(bytes + ' bytes written'); 16 | }; 17 | 18 | s.destroy = function () { 19 | s.writable = false; 20 | }; 21 | 22 | var fs = require('fs'); 23 | fs.createReadStream('/etc/passwd').pipe(s); 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stream-handbook", 3 | "version": "1.0.0", 4 | "description": "how to write node programs with streams", 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/substack/stream-handbook.git" 8 | }, 9 | "homepage": "https://github.com/substack/stream-handbook", 10 | "keywords": [ 11 | "documentation", 12 | "guide", 13 | "handbook", 14 | "stream", 15 | "streams" 16 | ], 17 | "author": { 18 | "name": "James Halliday", 19 | "email": "mail@substack.net", 20 | "url": "http://substack.net" 21 | }, 22 | "license": "cc-by-3.0", 23 | "dependencies": { 24 | "default-pager": "^1.0.1" 25 | }, 26 | "bin": { 27 | "stream-handbook": "bin/cmd.js" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /readme.markdown: -------------------------------------------------------------------------------- 1 | # Stream-handbook 2 | 3 | This document covers the basics of how to write [node.js](http://nodejs.org/) 4 | programs with [streams](http://nodejs.org/docs/latest/api/stream.html). 5 | You also could read a **[chinese edition](https://github.com/jabez128/stream-handbook)**. 6 | 7 | [![cc-by-3.0](http://i.creativecommons.org/l/by/3.0/80x15.png)](http://creativecommons.org/licenses/by/3.0/) 8 | 9 | # Node Packaged Manuscript 10 | 11 | You can install this handbook with npm. Just do: 12 | 13 | ``` js 14 | npm install -g stream-handbook 15 | ``` 16 | 17 | Now you will have a `stream-handbook` command that will open this readme file in 18 | your `$PAGER`. Otherwise, you may continue reading this document as you are 19 | presently doing. 20 | 21 | # Table of contents 22 | 23 | - [introduction](#Introduction) 24 | - [why you should use streams](#why-you-should-use-streams) 25 | - [basics](#basics) 26 | - [pipe](#pipe) 27 | - [readable streams](#readable-streams) 28 | - [writable streams](#writable-streams) 29 | - [transform](#transform) 30 | - [duplex](#duplex) 31 | - [classic streams](#classic-streams) 32 | - [read more](#read-more) 33 | - [built-in streams](#built-in-streams) 34 | - [process](#process) 35 | - [child_process.spawn()](#child_processspawn) 36 | - [fs](#fs) 37 | - [net](#net) 38 | - [http](#http) 39 | - [zlib](#zlib) 40 | - [control streams](#control-streams) 41 | - [meta streams](#meta-streams) 42 | - [state streams](#state-streams) 43 | - [http streams](#http-streams) 44 | - [io streams](#io-streams) 45 | - [parser streams](#parser-streams) 46 | - [browser streams](#browser-streams) 47 | - [html streams](#html-streams) 48 | - [audio streams](#audio-streams) 49 | - [rpc streams](#rpc-streams) 50 | - [test streams](#test-streams) 51 | - [power combos](#power-combos) 52 | 53 | # Introduction 54 | 55 | ``` 56 | "We should have some ways of connecting programs like garden hose--screw in 57 | another segment when it becomes necessary to massage data in 58 | another way. This is the way of IO also." 59 | ``` 60 | 61 | [Doug McIlroy. October 11, 1964](https://www.bell-labs.com/usr/dmr/www/mdmpipe.html) 62 | 63 | ![doug mcilroy](http://substack.net/images/mcilroy.png) 64 | 65 | *** 66 | 67 | Streams come to us from the 68 | [earliest days of Unix](http://www.youtube.com/watch?v=tc4ROCJYbm0) 69 | and have proven themselves over the decades as a dependable way to compose large 70 | systems out of small components that 71 | [do one thing well](http://www.faqs.org/docs/artu/ch01s06.html). 72 | In Unix, streams are implemented by the shell with `|` pipes. 73 | In node, the built-in 74 | [stream module](https://nodejs.org/docs/latest/api/stream.html) 75 | is used by the core libraries and can also be used by user-space modules. 76 | Similar to Unix, the node stream module's primary composition operator is called 77 | `.pipe()` and you get a backpressure mechanism for free to throttle writes for 78 | slow consumers. 79 | 80 | Streams can help to 81 | [separate your concerns](http://www.c2.com/cgi/wiki?SeparationOfConcerns) 82 | because they restrict the implementation surface area into a consistent 83 | interface that can be 84 | [reused](http://www.faqs.org/docs/artu/ch01s06.html#id2877537). 85 | You can then plug the output of one stream to the input of another and 86 | [use libraries](http://npmjs.org) that operate abstractly on streams to 87 | institute higher-level flow control. 88 | 89 | Streams are an important component of 90 | [small-program design](https://web.archive.org/web/20150910093715/https://michaelochurch.wordpress.com/2012/08/15/what-is-spaghetti-code/) (link updated thanks to https://github.com/substack/stream-handbook/pull/107) 91 | and [unix philosophy](http://www.faqs.org/docs/artu/ch01s06.html) 92 | but there are many other important abstractions worth considering. 93 | Just remember that [technical debt](http://c2.com/cgi/wiki?TechnicalDebt) 94 | is the enemy and to seek the best abstractions for the problem at hand. 95 | 96 | ![brian kernighan](http://substack.net/images/kernighan.png) 97 | 98 | *** 99 | 100 | # Why You Should Use Streams 101 | 102 | I/O in node is asynchronous, so interacting with the disk and network involves 103 | passing callbacks to functions. You might be tempted to write code that serves 104 | up a file from disk like this: 105 | 106 | ``` js 107 | var http = require('http'); 108 | var fs = require('fs'); 109 | 110 | var server = http.createServer(function (req, res) { 111 | fs.readFile(__dirname + '/data.txt', function (err, data) { 112 | res.end(data); 113 | }); 114 | }); 115 | server.listen(8000); 116 | ``` 117 | 118 | This code works but it's bulky and buffers up the entire `data.txt` file into 119 | memory for every request before writing the result back to clients. If 120 | `data.txt` is very large, your program could start eating a lot of memory as it 121 | serves lots of users concurrently, particularly for users on slow connections. 122 | 123 | The user experience is poor too because users will need to wait for the whole 124 | file to be buffered into memory on your server before they can start receiving 125 | any contents. 126 | 127 | Luckily both of the `(req, res)` arguments are streams, which means we can write 128 | this in a much better way using `fs.createReadStream()` instead of 129 | `fs.readFile()`: 130 | 131 | ``` js 132 | var http = require('http'); 133 | var fs = require('fs'); 134 | 135 | var server = http.createServer(function (req, res) { 136 | var stream = fs.createReadStream(__dirname + '/data.txt'); 137 | stream.pipe(res); 138 | }); 139 | server.listen(8000); 140 | ``` 141 | 142 | Here `.pipe()` takes care of listening for `'data'`, `'error'` and `'end'` events from the 143 | `fs.createReadStream()`. This code is not only cleaner, but now the `data.txt` 144 | file will be written to clients one chunk at a time immediately as each chunk 145 | is received from the disk. 146 | 147 | Using `.pipe()` has other benefits too, like handling backpressure automatically 148 | so that node won't buffer chunks into memory needlessly when the remote client 149 | is on a really slow or high-latency connection. 150 | 151 | Want compression? There are streaming modules for that too! 152 | 153 | ``` js 154 | var http = require('http'); 155 | var fs = require('fs'); 156 | var oppressor = require('oppressor'); 157 | 158 | var server = http.createServer(function (req, res) { 159 | var stream = fs.createReadStream(__dirname + '/data.txt'); 160 | stream.pipe(oppressor(req)).pipe(res); 161 | }); 162 | server.listen(8000); 163 | ``` 164 | 165 | Now our file is compressed for browsers that support gzip or deflate! We can 166 | just let [oppressor](https://github.com/substack/oppressor) handle all that 167 | content-encoding stuff. 168 | 169 | Don't forget to listen `'error'` event if you want to catch it 170 | ``` js 171 | var server = http.createServer(function (req, res) { 172 | var stream = fs.createReadStream(__dirname + '/data.txt'); 173 | stream.pipe(res); 174 | stream.on('error', function(err){ 175 | res.statusCode = 500; 176 | res.end('Internal Server Error'); 177 | }); 178 | }); 179 | ``` 180 | 181 | Once you learn the stream api, you can just snap together these streaming 182 | modules like lego bricks or garden hoses instead of having to remember how to push 183 | data through wonky non-streaming custom APIs. 184 | 185 | Streams make programming in node simple, elegant, and composable. 186 | 187 | # Basics 188 | 189 | There are 5 kinds of streams: readable, writable, transform, duplex, and 190 | "classic". 191 | 192 | ## Pipe 193 | 194 | All the different types of streams use `.pipe()` to pair inputs with outputs. 195 | 196 | `.pipe()` is just a function that takes a readable source stream `src` and hooks 197 | the output to a destination writable stream `dst`: 198 | 199 | ``` js 200 | src.pipe(dst) 201 | ``` 202 | 203 | `.pipe(dst)` returns `dst` so that you can chain together multiple `.pipe()` 204 | calls together: 205 | 206 | ``` js 207 | a.pipe(b).pipe(c).pipe(d) 208 | ``` 209 | which is the same as: 210 | 211 | ``` js 212 | a.pipe(b); 213 | b.pipe(c); 214 | c.pipe(d); 215 | ``` 216 | 217 | This is very much like what you might do on the command-line to pipe programs 218 | together: 219 | 220 | ``` js 221 | a | b | c | d 222 | ``` 223 | 224 | except in node instead of the shell! 225 | 226 | ## Readable Streams 227 | 228 | Readable streams produce data that can be fed into a writable, transform, or 229 | duplex stream by calling `.pipe()`: 230 | 231 | ``` js 232 | readableStream.pipe(dst) 233 | ``` 234 | 235 | ### Creating a Readable Stream 236 | 237 | Let's make a readable stream! 238 | 239 | https://github.com/dmitriz/stream-handbook/blob/master/example/basics/read0.js 240 | 241 | ``` js 242 | var Readable = require('stream').Readable; 243 | 244 | var rs = new Readable; 245 | // see https://nodejs.org/api/stream.html#stream_readable_push_chunk_encoding 246 | rs._read = function () {}; 247 | rs.push('beep '); 248 | rs.push('boop\n'); 249 | rs.push(null); 250 | 251 | rs.pipe(process.stdout); 252 | ``` 253 | 254 | ``` js 255 | $ node read0.js 256 | beep boop 257 | ``` 258 | 259 | `rs.push(null)` tells the consumer that `rs` is done outputting data. 260 | 261 | Note here that we pushed content to the readable stream `rs` before piping to 262 | `process.stdout`, but the complete message was still written. 263 | 264 | This is because when you `.push()` to a readable stream, the chunks you push are 265 | buffered until a consumer is ready to read them. 266 | 267 | However, it would be even better in many circumstances if we could avoid 268 | buffering data altogether and only generate the data when the consumer asks for 269 | it. 270 | 271 | We can push chunks on-demand by defining a `._read` function: 272 | 273 | https://github.com/dmitriz/stream-handbook/blob/master/example/basics/read1.js 274 | 275 | ``` js 276 | var Readable = require('stream').Readable; 277 | var rs = Readable(); 278 | 279 | var c = 97; 280 | rs._read = function () { 281 | rs.push(String.fromCharCode(c++)); 282 | if (c > 'z'.charCodeAt(0)) rs.push(null); 283 | }; 284 | 285 | rs.pipe(process.stdout); 286 | ``` 287 | 288 | ``` js 289 | $ node read1.js 290 | abcdefghijklmnopqrstuvwxyz 291 | ``` 292 | 293 | Here we push the letters `'a'` through `'z'`, inclusive, but only when the 294 | consumer is ready to read them. 295 | 296 | The `_read` function will also get a provisional `size` parameter as its first 297 | argument that specifies how many bytes the consumer wants to read, but your 298 | readable stream can ignore the `size` if it wants. 299 | 300 | Note that you can also use `util.inherits()` to subclass a Readable stream, but 301 | that approach doesn't lend itself very well to comprehensible examples. 302 | 303 | To show that our `_read` function is only being called when the consumer 304 | requests, we can modify our readable stream code slightly to add a delay: 305 | 306 | https://github.com/dmitriz/stream-handbook/blob/master/example/basics/read2.js 307 | 308 | ``` js 309 | var Readable = require('stream').Readable; 310 | var rs = Readable(); 311 | 312 | var count = 0; 313 | var c = 97 - 1; 314 | 315 | rs._read = function () { 316 | count++; 317 | if (c >= 'z'.charCodeAt(0)) return rs.push(null); 318 | 319 | setTimeout(function () { 320 | rs.push(String.fromCharCode(++c)); 321 | }, 100); 322 | }; 323 | 324 | rs.pipe(process.stdout); 325 | 326 | process.on('exit', function () { 327 | console.error('\n_read() called ' + count + ' times'); 328 | }); 329 | process.stdout.on('error', process.exit); 330 | ``` 331 | 332 | Running this program we can see that `_read()` is only called 5 times when we 333 | only request 5 bytes of output: 334 | 335 | ``` js 336 | $ node read2.js | head -c5 337 | abcde 338 | _read() called 5 times 339 | ``` 340 | 341 | The setTimeout delay is necessary because the operating system requires some 342 | time to send us the relevant signals to close the pipe. 343 | 344 | The `process.stdout.on('error', fn)` handler is also necessary because the 345 | operating system will send a SIGPIPE to our process when `head` is no longer 346 | interested in our program's output, which gets emitted as an EPIPE error on 347 | `process.stdout`. 348 | 349 | These extra complications are necessary when interfacing with the external 350 | operating system pipes but are automatic when we interface directly with node 351 | streams the whole time. 352 | 353 | If you want to create a readable stream that pushes arbitrary values instead of 354 | just strings and buffers, make sure to create your readable stream with 355 | `Readable({ objectMode: true })`. 356 | 357 | ### Consuming a Readable Stream 358 | 359 | Most of the time it's much easier to just pipe a readable stream into another 360 | kind of stream or a stream created with a module like 361 | [through](https://npmjs.org/package/through) 362 | or [concat-stream](https://npmjs.org/package/concat-stream), 363 | but occasionally it might be useful to consume a readable stream directly. 364 | 365 | https://github.com/dmitriz/stream-handbook/blob/master/example/basics/consume0.js 366 | 367 | ``` js 368 | process.stdin.on('readable', function () { 369 | var buf = process.stdin.read(); 370 | console.dir(buf); 371 | }); 372 | ``` 373 | 374 | ``` sh 375 | $ (echo abc; sleep 1; echo def; sleep 1; echo ghi) | node consume0.js 376 | 377 | 378 | 379 | null 380 | ``` 381 | 382 | When data is available, the `'readable'` event fires and you can call `.read()` 383 | to fetch some data from the buffer. 384 | 385 | When the stream is finished, `.read()` returns `null` because there are no more 386 | bytes to fetch. 387 | 388 | You can also tell `.read(n)` to return `n` bytes of data. Reading a number of 389 | bytes is merely advisory and does not work for object streams, but all of the 390 | core streams support it. 391 | 392 | Here's an example of using `.read(n)` to buffer stdin into 3-byte chunks: 393 | 394 | ``` js 395 | process.stdin.on('readable', function () { 396 | var buf = process.stdin.read(3); 397 | console.dir(buf); 398 | }); 399 | ``` 400 | 401 | Running this example gives us incomplete data! 402 | 403 | https://github.com/dmitriz/stream-handbook/blob/master/example/basics/consume1.js 404 | 405 | 406 | ``` sh 407 | $ (echo abc; sleep 1; echo def; sleep 1; echo ghi) | node consume1.js 408 | 409 | 410 | 411 | ``` 412 | 413 | This is because there is extra data left in internal buffers and we need to give 414 | node a "kick" to tell it that we are interested in more data past the 3 bytes 415 | that we've already read. A simple `.read(0)` will do this: 416 | 417 | https://github.com/dmitriz/stream-handbook/blob/master/example/basics/consume2.js 418 | 419 | 420 | ``` js 421 | process.stdin.on('readable', function () { 422 | var buf = process.stdin.read(3); 423 | console.dir(buf); 424 | process.stdin.read(0); 425 | }); 426 | ``` 427 | 428 | Now our code works as expected in 3-byte chunks! 429 | 430 | ``` sh 431 | $ (echo abc; sleep 1; echo def; sleep 1; echo ghi) | node consume2.js 432 | 433 | 434 | 435 | 436 | ``` 437 | 438 | You can also use `.unshift()` to put back data so that the same read logic will 439 | fire when `.read()` gives you more data than you wanted. 440 | 441 | Using `.unshift()` prevents us from making unnecessary buffer copies. Here we 442 | can build a readable parser to split on newlines: 443 | 444 | https://github.com/dmitriz/stream-handbook/blob/master/example/basics/lines.js 445 | 446 | ``` js 447 | var offset = 0; 448 | 449 | process.stdin.on('readable', function () { 450 | var buf = process.stdin.read(); 451 | if (!buf) return; 452 | for (; offset < buf.length; offset++) { 453 | if (buf[offset] === 0x0a) { 454 | console.dir(buf.slice(0, offset).toString()); 455 | buf = buf.slice(offset + 1); 456 | offset = 0; 457 | process.stdin.unshift(buf); 458 | return; 459 | } 460 | } 461 | console.dir(buf.toString()); 462 | }); 463 | ``` 464 | 465 | ``` 466 | $ tail -n +50000 /usr/share/dict/american-english | head -n10 | node lines.js 467 | 'hearties' 468 | 'heartiest' 469 | 'heartily' 470 | 'heartiness' 471 | 'heartiness\'s' 472 | 'heartland' 473 | 'heartland\'s' 474 | 'heartlands' 475 | 'heartless' 476 | 'heartlessly' 477 | ``` 478 | 479 | However, there are modules on npm such as 480 | [split](https://npmjs.org/package/split) that you should use instead of rolling 481 | your own line-parsing logic. 482 | 483 | ## Writable Streams 484 | 485 | A writable stream is a stream you can `.pipe()` to but not from: 486 | 487 | ``` js 488 | src.pipe(writableStream) 489 | ``` 490 | 491 | ### Creating a Writable Stream 492 | 493 | Just define a `._write(chunk, enc, next)` function and then you can pipe a 494 | readable stream in: 495 | 496 | https://github.com/dmitriz/stream-handbook/blob/master/example/basics/write0.js 497 | 498 | ``` js 499 | var Writable = require('stream').Writable; 500 | var ws = new Writable(); 501 | ws._write = function (chunk, enc, next) { 502 | console.dir(chunk); 503 | next(); 504 | }; 505 | 506 | process.stdin.pipe(ws); 507 | ``` 508 | 509 | ``` sh 510 | $ (echo beep; sleep 1; echo boop) | node write0.js 511 | 512 | 513 | ``` 514 | 515 | The first argument, `chunk` is the data that is written by the producer. 516 | 517 | The second argument `enc` is a string with the string encoding, but only when 518 | `opts.decodeString` is `false` and you've been written a string. 519 | 520 | The third argument, `next(err)` is the callback that tells the consumer that 521 | they can write more data. You can optionally pass an error object `err`, which 522 | emits an `'error'` event on the stream instance. 523 | 524 | If the readable stream you're piping from writes strings, they will be converted 525 | into `Buffer`s unless you create your writable stream with 526 | `Writable({ decodeStrings: false })`. 527 | 528 | If the readable stream you're piping from writes objects, create your writable 529 | stream with `Writable({ objectMode: true })`. 530 | 531 | ### Writing to a Writable Stream 532 | 533 | To write to a writable stream, just call `.write(data)` with the `data` you want 534 | to write! 535 | 536 | ``` js 537 | process.stdout.write('beep boop\n'); 538 | ``` 539 | 540 | To tell the destination writable stream that you're done writing, just call 541 | `.end()`. You can also give `.end(data)` some `data` to write before ending: 542 | 543 | https://github.com/dmitriz/stream-handbook/blob/master/example/basics/write1.js 544 | 545 | ``` js 546 | var fs = require('fs'); 547 | var ws = fs.createWriteStream('message.txt'); 548 | 549 | ws.write('beep '); 550 | 551 | setTimeout(function () { 552 | ws.end('boop\n'); 553 | }, 1000); 554 | ``` 555 | 556 | ``` sh 557 | $ node writing1.js 558 | $ cat message.txt 559 | beep boop 560 | ``` 561 | 562 | If you care about high water marks and buffering, `.write()` returns false when 563 | there is more data than the `opts.highWaterMark` option passed to `Writable()` 564 | in the incoming buffer. 565 | 566 | If you want to wait for the buffer to empty again, listen for a `'drain'` event. 567 | 568 | ## Transform 569 | 570 | Transform streams are a certain type of duplex stream (both readable and writable). 571 | The distinction is that in Transform streams, the output is in some way calculated 572 | from the input. 573 | 574 | You might also hear transform streams referred to as "through streams". 575 | 576 | ``` js 577 | a.pipe(transformStream).pipe(b); 578 | ``` 579 | 580 | Transform streams are one-way, requiring a read before a write. 581 | 582 | ### creating a transform stream 583 | Transform streams require a `._transform(chunk, enc, next)` function. For example, to make a simple transform stream that capitalizes text passed to it, you would write: 584 | 585 | https://github.com/dmitriz/stream-handbook/blob/master/example/basics/transform0.js 586 | 587 | ``` js 588 | var Transform = require('stream').Transform; 589 | 590 | var ts = Transform(); 591 | ts._transform = function(chunk, enc, next) { 592 | var data = chunk.toString(); 593 | data = data.toUpperCase(); 594 | this.push(data); // send the transformed data on its way 595 | next(); 596 | } 597 | 598 | process.on('exit', function() { 599 | console.error('\nYou cut me off!'); 600 | }); 601 | process.stdout.on('error', process.exit); 602 | 603 | process.stdin.pipe(ts).pipe(process.stdout); 604 | ``` 605 | 606 | ``` 607 | $ (echo hello world this is a test) | node transform0.js | head -c11 608 | HELLO WORLD 609 | You cut me off! 610 | ``` 611 | 612 | The first argument `chunk` is the chunk of data provided by the input stream for processing. 613 | 614 | The second argument `enc` is the encoding type of the incoming string, if applicable. 615 | 616 | The third argument `next(err)` should be called when you are done processing the incoming chunk and are ready for more. 617 | 618 | Optionally transform streams can also be given a `._flush(chunk, enc, next)` function. This function is called after all of the input chunks have been read, but before the final `end` signal is emitted signalling that the transform is complete. 619 | 620 | ## Duplex 621 | 622 | Duplex streams are readable/writable and both ends of the stream engage 623 | in a two-way interaction, sending back and forth messages like a telephone. An 624 | rpc exchange is a good example of a duplex stream. Any time you see something 625 | like: 626 | 627 | ``` js 628 | a.pipe(b).pipe(a) 629 | ``` 630 | 631 | you're probably dealing with a duplex stream. 632 | 633 | ## Classic Streams 634 | 635 | Classic streams are the old interface that first appeared in node 0.4. 636 | You will probably encounter this style of stream for a long time so it's good to 637 | know how they work. 638 | 639 | Whenever a stream has a `"data"` listener registered, it switches into 640 | `"classic"` mode and behaves according to the old API. 641 | 642 | ### Classic Readable Streams 643 | 644 | Classic readable streams are just event emitters that emit `"data"` events when 645 | they have data for their consumers and emit `"end"` events when they are done 646 | producing data for their consumers. 647 | 648 | `.pipe()` checks whether a classic stream is readable by checking the truthiness 649 | of `stream.readable`. 650 | 651 | Here is a super simple readable stream that prints `A` through `J`, inclusive: 652 | 653 | ``` js 654 | var Stream = require('stream'); 655 | var stream = new Stream; 656 | stream.readable = true; 657 | 658 | var c = 64; 659 | var iv = setInterval(function () { 660 | if (++c >= 75) { 661 | clearInterval(iv); 662 | stream.emit('end'); 663 | } 664 | else stream.emit('data', String.fromCharCode(c)); 665 | }, 100); 666 | 667 | stream.pipe(process.stdout); 668 | ``` 669 | 670 | ``` 671 | $ node classic0.js 672 | ABCDEFGHIJ 673 | ``` 674 | 675 | To read from a classic readable stream, you register `"data"` and `"end"` 676 | listeners. Here's an example reading from `process.stdin` using the old readable 677 | stream style: 678 | 679 | ``` js 680 | process.stdin.on('data', function (buf) { 681 | console.log(buf); 682 | }); 683 | process.stdin.on('end', function () { 684 | console.log('__END__'); 685 | }); 686 | ``` 687 | 688 | ``` sh 689 | $ (echo beep; sleep 1; echo boop) | node classic1.js 690 | 691 | 692 | __END__ 693 | ``` 694 | 695 | Note that whenever you register a `"data"` listener, you put the stream into 696 | compatibility mode so you lose the benefits of the new streams2 api. 697 | 698 | You should pretty much never register `"data"` and `"end"` handlers yourself 699 | anymore. If you need to interact with legacy streams, use libraries that you can 700 | `.pipe()` to instead where possible. 701 | 702 | For example, you can use [through](https://npmjs.org/package/through) 703 | to avoid setting up explicit `"data"` and `"end"` listeners: 704 | 705 | ``` js 706 | var through = require('through'); 707 | process.stdin.pipe(through(write, end)); 708 | 709 | function write (buf) { 710 | console.log(buf); 711 | } 712 | function end () { 713 | console.log('__END__'); 714 | } 715 | ``` 716 | 717 | ``` sh 718 | $ (echo beep; sleep 1; echo boop) | node through.js 719 | 720 | 721 | __END__ 722 | ``` 723 | 724 | or use [concat-stream](https://npmjs.org/package/concat-stream) to buffer up an 725 | entire stream's contents: 726 | 727 | ``` js 728 | var concat = require('concat-stream'); 729 | process.stdin.pipe(concat(function (body) { 730 | console.log(JSON.parse(body)); 731 | })); 732 | ``` 733 | 734 | ``` sh 735 | $ echo '{"beep":"boop"}' | node concat.js 736 | { beep: 'boop' } 737 | ``` 738 | 739 | Classic readable streams have `.pause()` and `.resume()` logic for provisionally 740 | pausing a stream, but this was merely advisory. If you are going to use 741 | `.pause()` and `.resume()` with classic readable streams, you should use 742 | [through](https://npmjs.org/package/through) to handle buffering instead of 743 | writing that yourself. 744 | 745 | ### Classic Writable Streams 746 | 747 | Classic writable streams are very simple. Just define `.write(buf)`, `.end(buf)` 748 | and `.destroy()`. 749 | 750 | `.end(buf)` may or may not get a `buf`, but node people will expect `stream.end(buf)` 751 | to mean `stream.write(buf); stream.end()` and you shouldn't violate their 752 | expectations. 753 | 754 | ## Read More 755 | 756 | * [Core stream documentation](http://nodejs.org/docs/latest/api/stream.html#stream_stream) 757 | * [Node Streams: How do they work?](http://maxogden.com/node-streams) 758 | * [Why streams are awesome](https://web.archive.org/web/20130125124400/http://blog.dump.ly/post/19819897856/why-node-js-streams-are-awesome) 759 | * [Hacker News: Why Node.js streams are awesome](https://news.ycombinator.com/item?id=3752340) 760 | * [The Power of NodeJS Streams and the event-stream Module](http://thlorenz.com/blog/the-power-of-nodejs-streams-and-the-event-stream-module/) 761 | 762 | *** 763 | 764 | # Built-in Streams 765 | 766 | These streams are built into node itself. 767 | 768 | ## Process 769 | 770 | ### [process.stdin](https://nodejs.org/docs/latest/api/process.html#process_process_stdin) 771 | 772 | This readable stream contains the standard system input stream for your program. 773 | 774 | It is paused by default but the first time you refer to it `.resume()` will be 775 | called implicitly on the 776 | [next tick](https://nodejs.org/docs/latest/api/process.html#process_process_nexttick_callback_arg). 777 | 778 | If process.stdin is a tty (check with 779 | [`tty.isatty()`](https://nodejs.org/docs/latest/api/tty.html#tty_tty_isatty_fd)) 780 | then input events will be line-buffered. You can turn off line-buffering by 781 | calling `process.stdin.setRawMode(true)` BUT the default handlers for key 782 | combinations such as `^C` and `^D` will be removed. 783 | 784 | ### [process.stdout](https://nodejs.org/api/process.html#process_process_stdout) 785 | 786 | This writable stream contains the standard system output stream for your program. 787 | 788 | `write` to it if you want to send data to stdout 789 | 790 | ### [process.stderr](https://nodejs.org/api/process.html#process_process_stderr) 791 | 792 | This writable stream contains the standard system error stream for your program. 793 | 794 | `write` to it if you want to send data to stderr 795 | 796 | ## child_process.spawn() 797 | 798 | ## fs 799 | 800 | ### fs.createReadStream() 801 | 802 | ### fs.createWriteStream() 803 | 804 | ## net 805 | 806 | ### [net.connect()](https://nodejs.org/docs/latest/api/net.html#net_net_connect_options_connectlistener) 807 | 808 | This function returns a [duplex stream] that connects over tcp to a remote 809 | host. 810 | 811 | You can start writing to the stream right away and the writes will be buffered 812 | until the `'connect'` event fires. 813 | 814 | ### net.createServer() 815 | 816 | ## http 817 | 818 | ### http.createServer() 819 | 820 | Create a new instance of `http.server`. Can create with optional callback 821 | function, `http.createServer([requestListener])`, which is called with every 822 | new client request. The `http.server` object created is an 823 | [EventEmitter](https://nodejs.org/api/events.html#events_class_events_eventemitter), 824 | This means bew request handlers can be bound to an existing server object using 825 | `.on()`. 826 | 827 | ### http.request() 828 | 829 | Returns an instance of the writable stream `ClientRequest`. May be passed with 830 | an optional callback. This callback will be used as a one time listener for the 831 | [`response`](https://nodejs.org/api/http.html#http_event_response) event. 832 | 833 | `http.request()` must be explicitly ended with a call to `req.end()`. 834 | 835 | ## zlib 836 | 837 | ### zlib.createGzip() 838 | 839 | ### zlib.createGunzip() 840 | 841 | ### zlib.createDeflate() 842 | 843 | ### zlib.createInflate() 844 | 845 | *** 846 | 847 | # Control Streams 848 | 849 | ## [through](https://github.com/dominictarr/through) 850 | 851 | ## [from](https://github.com/dominictarr/from) 852 | 853 | ## [pause-stream](https://github.com/dominictarr/pause-stream) 854 | ## [pipe-io](https://github.com/coderaiser/pipe-io) 855 | 856 | Create pipe between streams and adds callback wich would 857 | be called once whenever everything is done, or error occures. 858 | 859 | Pipes is a great and very fast way to connect a couple 860 | streams with each other, but error handling could be 861 | very annoying. If you forget to handle error on one of them 862 | your application would just crash with no call stask. 863 | 864 | Here is example of creating a `pipe` between two files with 865 | all cases taken into account. One would be read and second written. 866 | 867 | ```js 868 | var fs = require('fs'), 869 | read = fs.createReadStream('README.md'), 870 | write = fs.createWriteStream('README2.md'), 871 | 872 | open = function(msg) { 873 | read.pipe(write); 874 | }, 875 | finish = function() { 876 | console.log('done'); 877 | done(); 878 | }, 879 | error = function(e) { 880 | e && console.error(e.message); 881 | done(); 882 | }, 883 | 884 | done = function() { 885 | read.removeListener('error', error); 886 | write.removeListener('error', error); 887 | write.removeListener('open', open); 888 | write.removeListener('finish', finish); 889 | } 890 | 891 | read.on('error', e); 892 | write.on('error', e); 893 | 894 | write.on('open', open); 895 | write.on('finish', finish); 896 | ``` 897 | `pipe-io` helps write less code with same stability: 898 | 899 | ```js 900 | var fs = require('fs'), 901 | pipe = require('pipe-io'), 902 | read = fs.createReadStream('README.md'), 903 | write = fs.createWriteStream('README2.md'), 904 | error = function(e) { 905 | e && console.error(e.message); 906 | return e; 907 | } 908 | 909 | pipe([read, write], function(e) { 910 | error(e) || console.log('done'); 911 | }); 912 | ``` 913 | 914 | ## [concat-stream](https://github.com/maxogden/node-concat-stream) 915 | 916 | concat-stream will buffer up stream contents into a single buffer. 917 | `concat(cb)` just takes a single callback `cb(body)` with the buffered 918 | `body` when the stream has finished. 919 | 920 | For example, in this program, the concat callback fires with the body string 921 | `"beep boop"` once `cs.end()` is called. 922 | The program takes the body and upper-cases it, printing `BEEP BOOP.` 923 | 924 | ``` js 925 | var concat = require('concat-stream'); 926 | 927 | var cs = concat(function (body) { 928 | console.log(body.toUpperCase()); 929 | }); 930 | cs.write('beep '); 931 | cs.write('boop.'); 932 | cs.end(); 933 | ``` 934 | 935 | ``` sh 936 | $ node concat.js 937 | BEEP BOOP. 938 | ``` 939 | 940 | Here's an example usage of concat-stream that will parse incoming url-encoded 941 | form data and reply with a stringified JSON version of the form parameters: 942 | 943 | ``` js 944 | var http = require('http'); 945 | var qs = require('querystring'); 946 | var concat = require('concat-stream'); 947 | 948 | var server = http.createServer(function (req, res) { 949 | req.pipe(concat(function (body) { 950 | var params = qs.parse(body.toString()); 951 | res.end(JSON.stringify(params) + '\n'); 952 | })); 953 | }); 954 | server.listen(5005); 955 | ``` 956 | 957 | ``` sh 958 | $ curl -X POST -d 'beep=boop&dinosaur=trex' http://localhost:5005 959 | {"beep":"boop","dinosaur":"trex"} 960 | ``` 961 | 962 | ## [duplex](https://github.com/dominictarr/duplex) 963 | 964 | ## [duplexer](https://github.com/Raynos/duplexer) 965 | 966 | ## [duplexer2](https://github.com/deoxxa/duplexer2) 967 | 968 | ## [emit-stream](https://github.com/substack/emit-stream) 969 | 970 | ## [invert-stream](https://github.com/dominictarr/invert-stream) 971 | 972 | ## [map-stream](https://github.com/dominictarr/map-stream) 973 | 974 | ## [remote-events](https://github.com/dominictarr/remote-events) 975 | 976 | ## [buffer-stream](https://github.com/Raynos/buffer-stream) 977 | 978 | ## [event-stream](https://github.com/dominictarr/event-stream) 979 | 980 | ## [auth-stream](https://github.com/Raynos/auth-stream) 981 | 982 | ## [fork-stream](https://github.com/deoxxa/fork-stream) 983 | 984 | ## [combine-stream](https://github.com/deoxxa/combine-stream) 985 | 986 | ## [ternary-stream](https://github.com/robrich/ternary-stream) 987 | 988 | ## [ordered-through](https://github.com/juliangruber/ordered-through) 989 | 990 | *** 991 | 992 | # meta streams 993 | 994 | ## [mux-demux](https://github.com/dominictarr/mux-demux) 995 | 996 | ## [stream-router](https://github.com/Raynos/stream-router) 997 | 998 | ## [multi-channel-mdm](https://github.com/Raynos/multi-channel-mdm) 999 | 1000 | *** 1001 | 1002 | # state streams 1003 | 1004 | ## [crdt](https://github.com/dominictarr/crdt) 1005 | 1006 | ## [fsm-flow](https://github.com/ccortezia/fsm-flow) 1007 | 1008 | ## [delta-stream](https://github.com/Raynos/delta-stream) 1009 | 1010 | ## [scuttlebutt](https://github.com/dominictarr/scuttlebutt) 1011 | 1012 | [scuttlebutt](https://github.com/dominictarr/scuttlebutt) can be used for 1013 | peer-to-peer state synchronization with a mesh topology where nodes might only 1014 | be connected through intermediaries and there is no node with an authoritative 1015 | version of all the data. 1016 | 1017 | The kind of distributed peer-to-peer network that 1018 | [scuttlebutt](https://github.com/dominictarr/scuttlebutt) provides is especially 1019 | useful when nodes on different sides of network barriers need to share and 1020 | update the same state. An example of this kind of network might be browser 1021 | clients that send messages through an http server to each other and backend 1022 | processes that the browsers can't directly connect to. Another use-case might be 1023 | systems that span internal networks since IPv4 addresses are scarce. 1024 | 1025 | [scuttlebutt](https://github.com/dominictarr/scuttlebutt) uses a 1026 | [gossip protocol](https://en.wikipedia.org/wiki/Gossip_protocol) 1027 | to pass messages between connected nodes so that state across all the nodes will 1028 | [eventually converge](https://en.wikipedia.org/wiki/Eventual_consistency) 1029 | on the same value everywhere. 1030 | 1031 | Using the `scuttlebutt/model` interface, we can create some nodes and pipe them 1032 | to each other to create whichever sort of network we want: 1033 | 1034 | ``` js 1035 | var Model = require('scuttlebutt/model'); 1036 | var am = new Model; 1037 | var as = am.createStream(); 1038 | 1039 | var bm = new Model; 1040 | var bs = bm.createStream(); 1041 | 1042 | var cm = new Model; 1043 | var cs = cm.createStream(); 1044 | 1045 | var dm = new Model; 1046 | var ds = dm.createStream(); 1047 | 1048 | var em = new Model; 1049 | var es = em.createStream(); 1050 | 1051 | as.pipe(bs).pipe(as); 1052 | bs.pipe(cs).pipe(bs); 1053 | bs.pipe(ds).pipe(bs); 1054 | ds.pipe(es).pipe(ds); 1055 | 1056 | em.on('update', function (key, value, source) { 1057 | console.log(key + ' => ' + value + ' from ' + source); 1058 | }); 1059 | 1060 | am.set('x', 555); 1061 | ``` 1062 | 1063 | The network we've created is an undirected graph that looks like: 1064 | 1065 | ``` 1066 | a <-> b <-> c 1067 | ^ 1068 | | 1069 | v 1070 | d <-> e 1071 | ``` 1072 | 1073 | Note that nodes `a` and `e` aren't directly connected, but when we run this 1074 | script: 1075 | 1076 | ``` sh 1077 | $ node model.js 1078 | x => 555 from 1347857300518 1079 | ``` 1080 | 1081 | the value that node `a` set finds its way to node `e` by way of nodes `b` and 1082 | `d`. Here all the nodes are in the same process but because 1083 | [scuttlebutt](https://github.com/dominictarr/scuttlebutt) uses a 1084 | simple streaming interface, the nodes can be placed on any process or server and 1085 | connected with any streaming transport that can handle string data. 1086 | 1087 | Next we can make a more realistic example that connects over the network and 1088 | increments a counter variable. 1089 | 1090 | Here's the server which will set the initial `count` value to 0 and `count ++` 1091 | every 320 milliseconds, printing all updates to count: 1092 | 1093 | ``` js 1094 | var Model = require('scuttlebutt/model'); 1095 | var net = require('net'); 1096 | 1097 | var m = new Model; 1098 | m.set('count', '0'); 1099 | m.on('update', function (key, value) { 1100 | console.log(key + ' = ' + m.get('count')); 1101 | }); 1102 | 1103 | var server = net.createServer(function (stream) { 1104 | stream.pipe(m.createStream()).pipe(stream); 1105 | }); 1106 | server.listen(8888); 1107 | 1108 | setInterval(function () { 1109 | m.set('count', Number(m.get('count')) + 1); 1110 | }, 320); 1111 | ``` 1112 | 1113 | Now we can make a client that connects to this server, updates the count on an 1114 | interval, and prints all the updates it receives: 1115 | 1116 | ``` js 1117 | var Model = require('scuttlebutt/model'); 1118 | var net = require('net'); 1119 | 1120 | var m = new Model; 1121 | var s = m.createStream(); 1122 | 1123 | s.pipe(net.connect(8888, 'localhost')).pipe(s); 1124 | 1125 | m.on('update', function cb (key) { 1126 | // wait until we've gotten at least one count value from the network 1127 | if (key !== 'count') return; 1128 | m.removeListener('update', cb); 1129 | 1130 | setInterval(function () { 1131 | m.set('count', Number(m.get('count')) + 1); 1132 | }, 100); 1133 | }); 1134 | 1135 | m.on('update', function (key, value) { 1136 | console.log(key + ' = ' + value); 1137 | }); 1138 | ``` 1139 | 1140 | The client is slightly trickier since it should wait until it has an update from 1141 | somebody else to start updating the counter itself or else its counter would be 1142 | zeroed. 1143 | 1144 | Once we get the server and some clients running we should see a sequence like this: 1145 | 1146 | ``` js 1147 | count = 183 1148 | count = 184 1149 | count = 185 1150 | count = 186 1151 | count = 187 1152 | count = 188 1153 | count = 189 1154 | ``` 1155 | 1156 | Occasionally on some of the nodes we might see a sequence with repeated values like: 1157 | 1158 | ``` js 1159 | count = 147 1160 | count = 148 1161 | count = 149 1162 | count = 149 1163 | count = 150 1164 | count = 151 1165 | ``` 1166 | 1167 | These values are due to 1168 | [scuttlebutt's](https://github.com/dominictarr/scuttlebutt) 1169 | history-based conflict resolution algorithm which is hard at work ensuring that the state of the system across all nodes is eventually consistent. 1170 | 1171 | Note that the server in this example is just another node with the same 1172 | privileges as the clients connected to it. The terms "client" and "server" here 1173 | don't affect how the state synchronization proceeds, just who initiates the 1174 | connection. Protocols with this property are often called symmetric protocols. 1175 | See [dnode](https://github.com/substack/dnode) for another example of a 1176 | symmetric protocol. 1177 | 1178 | ## [append-only](http://github.com/Raynos/append-only) 1179 | 1180 | *** 1181 | 1182 | # http streams 1183 | 1184 | ## [request](https://github.com/mikeal/request) 1185 | 1186 | ## [oppressor](https://github.com/substack/oppressor) 1187 | 1188 | ## [response-stream](https://github.com/substack/response-stream) 1189 | 1190 | *** 1191 | 1192 | # io streams 1193 | 1194 | ## [reconnect](https://github.com/dominictarr/reconnect) 1195 | 1196 | ## [kv](https://github.com/dominictarr/kv) 1197 | 1198 | ## [discovery-network](https://github.com/Raynos/discovery-network) 1199 | 1200 | *** 1201 | 1202 | # parser streams 1203 | 1204 | ## [tar](https://github.com/creationix/node-tar) 1205 | 1206 | ## [trumpet](https://github.com/substack/node-trumpet) 1207 | 1208 | ## [JSONStream](https://github.com/dominictarr/JSONStream) 1209 | 1210 | Use this module to parse and stringify json data from streams. 1211 | 1212 | If you need to pass a large json collection through a slow connection or you 1213 | have a json object that will populate slowly this module will let you parse data 1214 | incrementally as it arrives. 1215 | 1216 | ## [json-scrape](https://github.com/substack/json-scrape) 1217 | 1218 | ## [stream-serializer](https://github.com/dominictarr/stream-serializer) 1219 | 1220 | *** 1221 | 1222 | # browser streams 1223 | 1224 | ## [shoe](https://github.com/substack/shoe) 1225 | 1226 | ## [domnode](https://github.com/maxogden/domnode) 1227 | 1228 | ## [sorta](https://github.com/substack/sorta) 1229 | 1230 | ## [graph-stream](https://github.com/substack/graph-stream) 1231 | 1232 | ## [arrow-keys](https://github.com/Raynos/arrow-keys) 1233 | 1234 | ## [attribute](https://github.com/Raynos/attribute) 1235 | 1236 | ## [data-bind](https://github.com/travis4all/data-bind) 1237 | 1238 | *** 1239 | 1240 | # html streams 1241 | 1242 | ## [hyperstream](https://github.com/substack/hyperstream) 1243 | 1244 | 1245 | # audio streams 1246 | 1247 | ## [baudio](https://github.com/substack/baudio) 1248 | 1249 | # rpc streams 1250 | 1251 | ## [dnode](https://github.com/substack/dnode) 1252 | 1253 | [dnode](https://github.com/substack/dnode) lets you call remote functions 1254 | through any kind of stream. 1255 | 1256 | Here's a basic dnode server: 1257 | 1258 | ``` js 1259 | var dnode = require('dnode'); 1260 | var net = require('net'); 1261 | 1262 | var server = net.createServer(function (c) { 1263 | var d = dnode({ 1264 | transform : function (s, cb) { 1265 | cb(s.replace(/[aeiou]{2,}/, 'oo').toUpperCase()) 1266 | } 1267 | }); 1268 | c.pipe(d).pipe(c); 1269 | }); 1270 | 1271 | server.listen(5004); 1272 | ``` 1273 | 1274 | then you can hack up a simple client that calls the server's `.transform()` 1275 | function: 1276 | 1277 | ``` js 1278 | var dnode = require('dnode'); 1279 | var net = require('net'); 1280 | 1281 | var d = dnode(); 1282 | d.on('remote', function (remote) { 1283 | remote.transform('beep', function (s) { 1284 | console.log('beep => ' + s); 1285 | d.end(); 1286 | }); 1287 | }); 1288 | 1289 | var c = net.connect(5004); 1290 | c.pipe(d).pipe(c); 1291 | ``` 1292 | 1293 | Fire up the server, then when you run the client you should see: 1294 | 1295 | ``` 1296 | $ node client.js 1297 | beep => BOOP 1298 | ``` 1299 | 1300 | The client sent `'beep'` to the server's `transform()` function and the server 1301 | called the client's callback with the result, neat! 1302 | 1303 | The streaming interface that dnode provides here is a duplex stream since both 1304 | the client and server are piped to each other (`c.pipe(d).pipe(c)`) with 1305 | requests and responses coming from both sides. 1306 | 1307 | The craziness of dnode begins when you start to pass function arguments to 1308 | stubbed callbacks. Here's an updated version of the previous server with a 1309 | multi-stage callback passing dance: 1310 | 1311 | ``` js 1312 | var dnode = require('dnode'); 1313 | var net = require('net'); 1314 | 1315 | var server = net.createServer(function (c) { 1316 | var d = dnode({ 1317 | transform : function (s, cb) { 1318 | cb(function (n, fn) { 1319 | var oo = Array(n+1).join('o'); 1320 | fn(s.replace(/[aeiou]{2,}/, oo).toUpperCase()); 1321 | }); 1322 | } 1323 | }); 1324 | c.pipe(d).pipe(c); 1325 | }); 1326 | 1327 | server.listen(5004); 1328 | ``` 1329 | 1330 | Here's the updated client: 1331 | 1332 | ``` js 1333 | var dnode = require('dnode'); 1334 | var net = require('net'); 1335 | 1336 | var d = dnode(); 1337 | d.on('remote', function (remote) { 1338 | remote.transform('beep', function (cb) { 1339 | cb(10, function (s) { 1340 | console.log('beep:10 => ' + s); 1341 | d.end(); 1342 | }); 1343 | }); 1344 | }); 1345 | 1346 | var c = net.connect(5004); 1347 | c.pipe(d).pipe(c); 1348 | ``` 1349 | 1350 | After we spin up the server, when we run the client now we get: 1351 | 1352 | ``` sh 1353 | $ node client.js 1354 | beep:10 => BOOOOOOOOOOP 1355 | ``` 1356 | 1357 | It just works!™ 1358 | 1359 | The basic idea is that you just put functions in objects and you call them from 1360 | the other side of a stream and the functions will be stubbed out on the other 1361 | end to do a round-trip back to the side that had the original function in the 1362 | first place. The best thing is that when you pass functions to a stubbed 1363 | function as arguments, those functions get stubbed out on the *other* side! 1364 | 1365 | This approach of stubbing function arguments recursively shall henceforth be 1366 | known as the "turtles all the way down" gambit. The return values of any of your 1367 | functions will be ignored and only enumerable properties on objects will be 1368 | sent, json-style. 1369 | 1370 | It's turtles all the way down! 1371 | 1372 | ![turtles all the way](http://substack.net/images/all_the_way_down.png) 1373 | 1374 | Since dnode works in node or on the browser over any stream it's easy to call 1375 | functions defined anywhere and especially useful when paired up with 1376 | [mux-demux](https://github.com/dominictarr/mux-demux) to multiplex an rpc stream 1377 | for control alongside some bulk data streams. 1378 | 1379 | ## [rpc-stream](https://github.com/dominictarr/rpc-stream) 1380 | 1381 | *** 1382 | 1383 | # test streams 1384 | 1385 | ## [tap](https://github.com/isaacs/node-tap) 1386 | 1387 | ## [stream-spec](https://github.com/dominictarr/stream-spec) 1388 | 1389 | ## [random-stream](https://github.com/bpostlethwaite/random-stream) 1390 | *** 1391 | 1392 | # power combos 1393 | 1394 | ## mississippi 1395 | 1396 | The [`mississippi` stream utility collection](https://github.com/maxogden/mississippi) demonstrates how to write better steam code using the modules [`pump`](https://npmjs.org/pump), [`stream-each`](https://npmjs.org/stream-each), [`pumpify`](https://npmjs.org/pumpify), [`duplexify`](https://npmjs.org/duplexify), [`through2`](https://npmjs.org/through2), [`concat-stream`](https://npmjs.org/concat-stream) and [`end-of-stream`](https://npmjs.org/end-of-stream). 1397 | 1398 | ## distributed partition-tolerant chat 1399 | 1400 | The [append-only](http://github.com/Raynos/append-only) module can give us a 1401 | convenient append-only array on top of 1402 | [scuttlebutt](https://github.com/dominictarr/scuttlebutt) 1403 | which makes it really easy to write an eventually-consistent, distributed chat 1404 | that can replicate with other nodes and survive network partitions. 1405 | 1406 | TODO: the rest 1407 | 1408 | ## roll your own socket.io 1409 | 1410 | We can build a socket.io-style event emitter api over streams using some of the 1411 | libraries mentioned earlier in this document. 1412 | 1413 | First we can use [shoe](http://github.com/substack/shoe) 1414 | to create a new websocket handler server-side and 1415 | [emit-stream](https://github.com/substack/emit-stream) 1416 | to turn an event emitter into a stream that emits objects. 1417 | The object stream can then be fed into 1418 | [JSONStream](https://github.com/dominictarr/JSONStream) 1419 | to serialize the objects and from there the serialized stream can be piped into 1420 | the remote browser. 1421 | 1422 | ``` js 1423 | var EventEmitter = require('events').EventEmitter; 1424 | var shoe = require('shoe'); 1425 | var emitStream = require('emit-stream'); 1426 | var JSONStream = require('JSONStream'); 1427 | 1428 | var sock = shoe(function (stream) { 1429 | var ev = new EventEmitter; 1430 | emitStream(ev) 1431 | .pipe(JSONStream.stringify()) 1432 | .pipe(stream) 1433 | ; 1434 | ... 1435 | }); 1436 | ``` 1437 | 1438 | Inside the shoe callback we can emit events to the `ev` function. Here we'll 1439 | just emit different kinds of events on intervals: 1440 | 1441 | ``` js 1442 | var intervals = []; 1443 | 1444 | intervals.push(setInterval(function () { 1445 | ev.emit('upper', 'abc'); 1446 | }, 500)); 1447 | 1448 | intervals.push(setInterval(function () { 1449 | ev.emit('lower', 'def'); 1450 | }, 300)); 1451 | 1452 | stream.on('end', function () { 1453 | intervals.forEach(clearInterval); 1454 | }); 1455 | ``` 1456 | 1457 | Finally the shoe instance just needs to be bound to an http server: 1458 | 1459 | ``` js 1460 | var http = require('http'); 1461 | var server = http.createServer(require('ecstatic')(__dirname)); 1462 | server.listen(8080); 1463 | 1464 | sock.install(server, '/sock'); 1465 | ``` 1466 | 1467 | Meanwhile on the browser side of things just parse the json shoe stream and pass 1468 | the resulting object stream to `emitStream()`. `emitStream()` just returns an 1469 | event emitter that emits the server-side events: 1470 | 1471 | ``` js 1472 | var shoe = require('shoe'); 1473 | var emitStream = require('emit-stream'); 1474 | var JSONStream = require('JSONStream'); 1475 | 1476 | var parser = JSONStream.parse([true]); 1477 | var stream = parser.pipe(shoe('/sock')).pipe(parser); 1478 | var ev = emitStream(stream); 1479 | 1480 | ev.on('lower', function (msg) { 1481 | var div = document.createElement('div'); 1482 | div.textContent = msg.toLowerCase(); 1483 | document.body.appendChild(div); 1484 | }); 1485 | 1486 | ev.on('upper', function (msg) { 1487 | var div = document.createElement('div'); 1488 | div.textContent = msg.toUpperCase(); 1489 | document.body.appendChild(div); 1490 | }); 1491 | ``` 1492 | 1493 | Use [browserify](https://github.com/substack/node-browserify) to build this 1494 | browser source code so that you can `require()` all these nifty modules 1495 | browser-side: 1496 | 1497 | ``` 1498 | $ browserify main.js -o bundle.js 1499 | ``` 1500 | 1501 | Then drop a `` into some html and open it up 1502 | in a browser to see server-side events streamed through to the browser side of 1503 | things. 1504 | 1505 | With this streaming approach you can rely more on tiny reusable components that 1506 | only need to know how to talk streams. Instead of routing messages through a 1507 | global event system socket.io-style, you can focus more on breaking up your 1508 | application into tinier units of functionality that can do exactly one thing 1509 | well. 1510 | 1511 | For instance you can trivially swap out JSONStream in this example for 1512 | [stream-serializer](https://github.com/dominictarr/stream-serializer) 1513 | to get a different take on serialization with a different set of tradeoffs. 1514 | You could bolt layers over top of shoe to handle 1515 | [reconnections](https://github.com/dominictarr/reconnect) or heartbeats 1516 | using simple streaming interfaces. 1517 | You could even add a stream into the chain to use namespaced events with 1518 | [eventemitter2](https://npmjs.org/package/eventemitter2) instead of the 1519 | EventEmitter in core. 1520 | 1521 | If you want some different streams that act in different ways it would likewise 1522 | be pretty simple to run the shoe stream in this example through mux-demux to 1523 | create separate channels for each different kind of stream that you need. 1524 | 1525 | As the requirements of your system evolve over time, you can swap out each of 1526 | these streaming pieces as necessary without as many of the all-or-nothing risks 1527 | that more opinionated framework approaches necessarily entail. 1528 | 1529 | ## html streams for the browser and the server 1530 | 1531 | We can use some streaming modules to reuse the same html rendering logic for the 1532 | client and the server! This approach is indexable, SEO-friendly, and gives us 1533 | realtime updates. 1534 | 1535 | Our renderer takes lines of json as input and returns html strings as its 1536 | output. Text, the universal interface! 1537 | 1538 | render.js: 1539 | 1540 | ``` js 1541 | var through = require('through'); 1542 | var hyperglue = require('hyperglue'); 1543 | var fs = require('fs'); 1544 | var html = fs.readFileSync(__dirname + '/static/row.html', 'utf8'); 1545 | 1546 | module.exports = function () { 1547 | return through(function (line) { 1548 | try { var row = JSON.parse(line) } 1549 | catch (err) { return this.emit('error', err) } 1550 | 1551 | this.queue(hyperglue(html, { 1552 | '.who': row.who, 1553 | '.message': row.message 1554 | }).outerHTML); 1555 | }); 1556 | }; 1557 | ``` 1558 | 1559 | We can use [brfs](http://github.com/substack/brfs) to inline the 1560 | `fs.readFileSync()` call for browser code 1561 | and [hyperglue](https://github.com/substack/hyperglue) to update html based on 1562 | css selectors. You don't need to use hyperglue necessarily here; anything that 1563 | can return a string with html in it will work. 1564 | 1565 | The `row.html` used is just a really simple stub thing: 1566 | 1567 | row.html: 1568 | 1569 | ``` html 1570 |
1571 |
1572 |
1573 |
1574 | ``` 1575 | 1576 | The server will just use [slice-file](https://github.com/substack/slice-file) to 1577 | keep everything simple. [slice-file](https://github.com/substack/slice-file) is 1578 | little more than a glorified `tail/tail -f` api but the interfaces map well to 1579 | databases with regular results plus a changes feed like couchdb. 1580 | 1581 | server.js: 1582 | 1583 | ``` js 1584 | var http = require('http'); 1585 | var fs = require('fs'); 1586 | var hyperstream = require('hyperstream'); 1587 | var ecstatic = require('ecstatic')(__dirname + '/static'); 1588 | 1589 | var sliceFile = require('slice-file'); 1590 | var sf = sliceFile(__dirname + '/data.txt'); 1591 | 1592 | var render = require('./render'); 1593 | 1594 | var server = http.createServer(function (req, res) { 1595 | if (req.url === '/') { 1596 | var hs = hyperstream({ 1597 | '#rows': sf.slice(-5).pipe(render()) 1598 | }); 1599 | hs.pipe(res); 1600 | fs.createReadStream(__dirname + '/static/index.html').pipe(hs); 1601 | } 1602 | else ecstatic(req, res) 1603 | }); 1604 | server.listen(8000); 1605 | 1606 | var shoe = require('shoe'); 1607 | var sock = shoe(function (stream) { 1608 | sf.follow(-1,0).pipe(stream); 1609 | }); 1610 | sock.install(server, '/sock'); 1611 | ``` 1612 | 1613 | The first part of the server handles the `/` route and streams the last 5 lines 1614 | from `data.txt` into the `#rows` div. 1615 | 1616 | The second part of the server handles realtime updates to `#rows` using 1617 | [shoe](http://github.com/substack/shoe), a simple streaming websocket polyfill. 1618 | 1619 | Next we can write some simple browser code to populate the realtime updates 1620 | from [shoe](http://github.com/substack/shoe) into the `#rows` div: 1621 | 1622 | ``` js 1623 | var through = require('through'); 1624 | var render = require('./render'); 1625 | 1626 | var shoe = require('shoe'); 1627 | var stream = shoe('/sock'); 1628 | 1629 | var rows = document.querySelector('#rows'); 1630 | stream.pipe(render()).pipe(through(function (html) { 1631 | rows.innerHTML += html; 1632 | })); 1633 | ``` 1634 | 1635 | Just compile with [browserify](http://browserify.org) and 1636 | [brfs](http://github.com/substack/brfs): 1637 | 1638 | ``` js 1639 | $ browserify -t brfs browser.js > static/bundle.js 1640 | ``` 1641 | 1642 | And that's it! Now we can populate `data.txt` with some silly data: 1643 | 1644 | ``` 1645 | $ echo '{"who":"substack","message":"beep boop."}' >> data.txt 1646 | $ echo '{"who":"zoltar","message":"COWER PUNY HUMANS"}' >> data.txt 1647 | ``` 1648 | 1649 | then spin up the server: 1650 | 1651 | ``` sh 1652 | $ node server.js 1653 | ``` 1654 | 1655 | then navigate to `localhost:8000` where we will see our content. If we add some 1656 | more content: 1657 | 1658 | ``` sh 1659 | $ echo '{"who":"substack","message":"oh hello."}' >> data.txt 1660 | $ echo '{"who":"zoltar","message":"HEAR ME!"}' >> data.txt 1661 | ``` 1662 | 1663 | then the page updates automatically with the realtime updates, hooray! 1664 | 1665 | We're now using exactly the same rendering logic on both the client and the 1666 | server to serve up SEO-friendly, indexable realtime content. Hooray! 1667 | --------------------------------------------------------------------------------