UNPKG

ts-stream

Version:

Type-safe object streams with seamless support for backpressure, ending, and error handling

198 lines 7.62 kB
"use strict"; /** * Promise-based object stream with seamless support for back-pressure and error * handling, written in Typescript. * * Copyright (C) 2015 Martin Poelstra * License: MIT */ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.pipeToNodeStream = exports.FileSink = exports.NodeReadable = void 0; var fs = require("fs"); var NodeStream = require("stream"); var Stream_1 = require("./Stream"); var util_1 = require("./util"); /** * Convert ts-stream into a Node.JS Readable instance. * * Usage example: * let sink = fs.createWriteStream("test.txt"); * let tsSource = Stream.from(["abc", "def"]); * let source = new NodeReadable(tsSource); * source.pipe(sink); * sink.on("error", (error: Error) => tsSource.abort(error)); * source.on("error", (error: Error) => { something like sink.destroy(); }); * * @see `pipeToNodeStream()` for easier error and completion handling. */ var NodeReadable = /** @class */ (function (_super) { __extends(NodeReadable, _super); /** * Create new NodeJS Readable based on given ts-stream Readable. * * @see class description for usage example * * @param tsReadable Source stream * @param options Optional options to pass to Node.JS Readable constructor */ function NodeReadable(tsReadable, options) { var _this = _super.call(this, options) || this; util_1.swallowErrors(tsReadable.forEach(function (chunk) { // Try to push data, returns true if there's still space if (_this.push(chunk)) { return; } // Stream blocked, wait until _read() is called var d = util_1.defer(); _this._resumer = d.resolve; return d.promise; }, function (err) { if (err) { _this.emit("error", err); } _this.push(null); // tslint:disable-line:no-null-keyword }, function (abortError) { // Abort pending read, if necessary if (_this._resumer) { _this._resumer(Promise.reject(abortError)); _this._resumer = undefined; } })); return _this; } NodeReadable.prototype._read = function (size) { if (this._resumer) { this._resumer(); this._resumer = undefined; } }; return NodeReadable; }(NodeStream.Readable)); exports.NodeReadable = NodeReadable; /** * Convenience wrapper around Node's file stream. * * Usage example: * let source = Stream.from(["abc", "def"]); * source.pipe(new FileSink("test.txt")); * * To wait for the stream's result, use e.g. * let sink = source.pipe(new FileSink("test.txt")); * sink.result().then(() => console.log("ok"), (err) => console.log("error", err)); */ var FileSink = /** @class */ (function (_super) { __extends(FileSink, _super); /** * Construct writable ts-stream which writes all values to given file. * If the stream is ended with an error, the file is closed (and `result()`) * reflects that error. * * @see class description for usage example * * @param path Filename to wite to * @param options Optional options (see https://nodejs.org/api/fs.html#fs_fs_createwritestream_path_options) */ function FileSink(path, options) { var _this = _super.call(this) || this; pipeToNodeStream(_this, fs.createWriteStream(path, options)); return _this; } return FileSink; }(Stream_1.Stream)); exports.FileSink = FileSink; /** * Pipe a ts-stream ReadableStream to a Node.JS WritableStream. * * Reads all values from `tsReadable` and writes them to `nodeWritable`. * When readable ends, writable is also ended. * * If an error occurs on the writable stream, the readable stream is aborted * with that error. * If the readable stream is ended with an error, that error is optionally * emitted on the writable stream, and then the writable stream is ended. * * Usage example: * let sink = fs.createWriteStream("test.txt"); * let source = Stream.from(["abc", "def"]); * let result = pipeToNodeStream(source, sink); * result.then(() => console.log("done"), (err) => console.log(err)); * * @see `NodeReadable` if you need an instance of a Node.JS ReadableStream * * @param tsReadable Source stream * @param nodeWritable Destination stream * @param emitError Whether to emit errors in tsReadable on nodeWritable * (default false). Useful for e.g. destroying a socket * when an error occurs. * @return Promise that resolves when stream is finished (rejected when an error * occurred) */ function pipeToNodeStream(tsReadable, nodeWritable, emitError) { if (emitError === void 0) { emitError = false; } var endDeferred = util_1.defer(); var blockedDeferred; // Handle errors emitted by node stream: abort ts-stream function handleNodeStreamError(error) { // Don't 're-emit' the same error on which we were triggered emitError = false; // Make sure stream's end result reflects error endDeferred.reject(error); tsReadable.abort(error); if (blockedDeferred) { blockedDeferred.reject(error); nodeWritable.removeListener("drain", blockedDeferred.resolve); } } // Optionally pass ts-stream errors to node stream function handleTsStreamError(error) { if (!emitError) { return; } emitError = false; nodeWritable.removeListener("error", handleNodeStreamError); // prevent abort nodeWritable.emit("error", error); } nodeWritable.once("error", handleNodeStreamError); nodeWritable.once("finish", function () { nodeWritable.removeListener("error", handleNodeStreamError); endDeferred.resolve(); // ignored if error happens earlier }); return tsReadable.forEach(function (chunk) { blockedDeferred = undefined; // Try to push data, returns true if there's still space var canAcceptMore = nodeWritable.write(chunk); if (!canAcceptMore) { // Stream blocked, wait until drain is emitted blockedDeferred = util_1.defer(); nodeWritable.once("drain", blockedDeferred.resolve); return blockedDeferred.promise; } }, function (endError) { if (endError) { handleTsStreamError(endError); } // Note: we don't pass a callback to end(), as some types of // streams (e.g. sockets) may forget to call the callback in // some scenarios (e.g. connection reset). // We use the "finish" event to mark the stream's end, but it // doesn't get emitted on e.g. a connection refused error. nodeWritable.end(); return endDeferred.promise; }, handleTsStreamError // abort handler ); } exports.pipeToNodeStream = pipeToNodeStream; //# sourceMappingURL=node.js.map