ts-stream
Version:
Type-safe object streams with seamless support for backpressure, ending, and error handling
198 lines • 7.62 kB
JavaScript
;
/**
* 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