UNPKG

apikana

Version:

Integrated tools for REST API design - アピ

172 lines (154 loc) 5.37 kB
;"use strict"; exports.createStringWritable = createStringWritable; exports.streamConcat = streamConcat; exports.streamFromError = streamFromError; exports.streamFromString = streamFromString; exports.emptyStream = emptyStream; exports.createLinePrefixStream = createLinePrefixStream; // Private //////////////////////////////////////////////////////////////////// const Stream = require("stream"); /** * @return {Writable|Promise} * A writable stream with a mixed in promise. The promise will resolve * with the written content as string as soon the writable has finished. */ function createStringWritable() { var fulfill, reject; var promise = new Promise(function( f , r ){ fulfill=f; reject=r; }); var writable = new Stream.Writable(); var chunks = []; Object.defineProperties( writable , { _write: { value: function( chunk , encoding , done ) { chunks.push( chunk.toString() ); setImmediate( done ); }}, then: { value: promise.then.bind(promise) }, "catch": { value: promise["catch"].bind(promise) }, }); writable.on( "finish" , function() { setImmediate( fulfill , chunks.join("") ); chunks = null; }); // HINT: This doesn't work. Errors from source will never pass here. Instead, // client has to register a handler for 'error' event before pipe to us. writable.on( "error" , function( err ) { setImmediate( reject , err ); chunks = null; }); return writable; } /** * @param streams {Array<Readable>} * @param [options={}] * @param [options.finalize=true] {boolean} * Define if the stream gets closed when all specified streams got * forwarded. * @param [options.objectMode=false] {boolean} * Specifies behavior for this stream as specified by nodejs streams. * @return {Readable} * A readable containing all passed in readables. */ function streamConcat( streams , options ){ if( !options ) options = {}; const objectMode = (options.objectMode === undefined ? false : options.objectMode ); const finalize = (options.finalize === undefined) ? true : options.finalize; options = null; streams = streams.slice( 0 ); // Make a copy to resist changes from outside. const that = new Stream.Readable({ objectMode:objectMode , read:read }); var isRunning = false; return that; function read( n ){ if( isRunning ){ return; }else{ isRunning=true; } (function handleNextSrcStream(){ const stream = streams.shift(); if( stream ){ stream.on( "data" , that.push.bind(that) ); stream.on( "end" , handleNextSrcStream ); stream.on( "error" , that.emit.bind(that,"error") ); }else{ // All inputs streamed. if( finalize ){ that.push( null ); } } }()); } } /** * @return {Readable} * An empty Readable. */ function emptyStream() { return new Stream.Readable({ read:function(){ this.push(null); }}); } /** * @param err * The error to use as only error element for returned readable. * @return {Readable} * A readable which will result in specified error. */ function streamFromError( err ){ var isRunning = false; return new Stream.Readable({ read:function(){ if( isRunning ){ return; }else{ isRunning=true; } setImmediate( this.emit.bind(this,"error",err) ); }}); } /** * @param str {string} * @return {Readable<Buffer>} * A Readable which streams the passed string. */ function streamFromString( str ) { const that = new Stream.Readable({ read:read }); return that; function read( n ){ that.push( str ); that.push( null ); } } /** * @param options.prefix {string} * The string to insert before each line. * @return {stream.Duplex} * The stream inserting the configured prefix. */ function createLinePrefixStream( options ){ if( !options ) options = {}; const prefix = options.prefix; if( typeof(prefix) !== "string" ){ throw TypeError("Arg 'prefix' expected to be string."); } options = null; // Prevent later usage. const that = new Stream.Duplex({ read:read , write:onInput }); var previousCharWasNewline = true; that.on( "finish" , that.push.bind(that,null) ); return that; function onInput( chunk , enc , ready ){ var begin = 0; for( var i=0 ; i<chunk.length ; ++i ){ if( previousCharWasNewline ){ that.push( prefix ); previousCharWasNewline = false; } if( chunk[i] === 10 ){ var sub = chunk.slice( begin , i+1 ); that.push( sub ); begin = i+1; previousCharWasNewline = true; } } if( begin < i ){ if( previousCharWasNewline ){ that.push( prefix ); } that.push(chunk.slice(begin)); if( chunk[chunk.length-1] == 10 ){ previousCharWasNewline = true; }else{ previousCharWasNewline = false; } } ready(); } function read( n ) { // Empty, because we don't support back-pressure yet. } }