mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-27 06:43:19 +08:00
142 lines
4.8 KiB
JavaScript
142 lines
4.8 KiB
JavaScript
// Writable and Duplex streams built by constructor functions, the bl and
|
|
// tar-stream patterns: Writable.call(this, opts), Duplex.call(this),
|
|
// _write/_final/_read on the prototype, listeners added before the base
|
|
// constructor runs, and an end() override that calls the base's end.
|
|
const stream = require("stream");
|
|
const util = require("util");
|
|
|
|
function Sink(opts) {
|
|
if (!(this instanceof Sink)) return new Sink(opts);
|
|
stream.Writable.call(this, opts);
|
|
this.chunks = [];
|
|
this.finalized = false;
|
|
}
|
|
util.inherits(Sink, stream.Writable);
|
|
Sink.prototype._write = function (chunk, enc, cb) {
|
|
console.log("_write", this instanceof Sink, enc, Buffer.isBuffer(chunk), chunk.toString());
|
|
this.chunks.push(chunk);
|
|
if (this.chunks.length === 2) setImmediate(cb);
|
|
else cb();
|
|
};
|
|
Sink.prototype._final = function (cb) {
|
|
console.log("_final", this.chunks.length);
|
|
this.finalized = true;
|
|
cb();
|
|
};
|
|
|
|
const sink = new Sink({ highWaterMark: 8 });
|
|
console.log("writable", sink.writable, sink.writableHighWaterMark, sink._writableState.highWaterMark);
|
|
console.log("write a", sink.write("first"));
|
|
console.log("write b", sink.write(Buffer.from("second"), () => console.log("write b callback")));
|
|
console.log("needDrain", sink.writableNeedDrain, sink.writableLength);
|
|
sink.on("drain", () => console.log("drain", sink.writableLength));
|
|
sink.on("finish", function () {
|
|
console.log("finish", this === sink, sink.finalized, sink.writableFinished, Buffer.concat(sink.chunks).toString());
|
|
});
|
|
sink.on("close", () => {
|
|
console.log("close", sink.destroyed, sink.closed);
|
|
duplex();
|
|
});
|
|
sink.end("third", (err) => console.log("end callback", err, sink.writableEnded, sink._writableState.ended));
|
|
console.log("after end", sink.writableEnded, sink.writable);
|
|
|
|
// A bl-style list: a Duplex that collects writes and reads them back.
|
|
function BufferListStream(callback) {
|
|
if (!(this instanceof BufferListStream)) return new BufferListStream(callback);
|
|
if (typeof callback === "function") {
|
|
this._callback = callback;
|
|
const piper = function piper(err) {
|
|
if (this._callback) {
|
|
this._callback(err);
|
|
this._callback = null;
|
|
}
|
|
}.bind(this);
|
|
this.on("pipe", function onPipe(src) {
|
|
console.log("pipe event", src instanceof stream.Readable);
|
|
src.on("error", piper);
|
|
});
|
|
this.on("unpipe", function onUnpipe(src) {
|
|
console.log("unpipe event");
|
|
src.removeListener("error", piper);
|
|
});
|
|
callback = null;
|
|
}
|
|
this._bufs = [];
|
|
this.length = 0;
|
|
stream.Duplex.call(this);
|
|
}
|
|
util.inherits(BufferListStream, stream.Duplex);
|
|
BufferListStream.prototype._write = function (buf, encoding, callback) {
|
|
this._bufs.push(buf);
|
|
this.length += buf.length;
|
|
callback();
|
|
};
|
|
BufferListStream.prototype._read = function (size) {
|
|
if (!this.length) return this.push(null);
|
|
const all = Buffer.concat(this._bufs);
|
|
size = Math.min(size, this.length, 4);
|
|
this.push(all.subarray(0, size));
|
|
this._bufs = [all.subarray(size)];
|
|
this.length -= size;
|
|
};
|
|
BufferListStream.prototype.end = function end(chunk) {
|
|
stream.Duplex.prototype.end.call(this, chunk);
|
|
if (this._callback) {
|
|
this._callback(null, Buffer.concat(this._bufs));
|
|
this._callback = null;
|
|
}
|
|
};
|
|
BufferListStream.prototype.slice = function () {
|
|
return Buffer.concat(this._bufs);
|
|
};
|
|
|
|
function Letters(text) {
|
|
stream.Readable.call(this, { highWaterMark: 2 });
|
|
this._text = text;
|
|
}
|
|
util.inherits(Letters, stream.Readable);
|
|
Letters.prototype._read = function () {
|
|
const next = this._text.slice(0, 3);
|
|
this._text = this._text.slice(3);
|
|
this.push(next.length ? next : null);
|
|
};
|
|
|
|
function duplex() {
|
|
const list = new BufferListStream((err, data) => {
|
|
console.log("bl callback", err, data.toString(), data.length);
|
|
});
|
|
console.log("duplex", list.readable, list.writable, list.allowHalfOpen, list instanceof stream.Duplex);
|
|
console.log("pre-constructor listeners", list.listenerCount("pipe"), list.listenerCount("unpipe"));
|
|
list.write("hello ");
|
|
list.write("constructor ");
|
|
list.end("functions");
|
|
list.on("finish", () => {
|
|
const out = [];
|
|
list.on("data", (d) => out.push(d.toString()));
|
|
list.on("end", () => {
|
|
console.log("read back", JSON.stringify(out));
|
|
piping();
|
|
});
|
|
});
|
|
}
|
|
|
|
function piping() {
|
|
const src = new Letters("abcdefghij");
|
|
const dst = new Sink({ highWaterMark: 1 });
|
|
dst.on("pipe", (from) => console.log("dst pipe", from === src));
|
|
const returned = src.pipe(dst);
|
|
console.log("pipe returns dest", returned === dst);
|
|
dst.on("finish", () => {
|
|
console.log("piped", Buffer.concat(dst.chunks).toString(), src.readableEnded);
|
|
const keep = new Sink();
|
|
const from = new Letters("xyz");
|
|
from.pipe(keep, { end: false });
|
|
from.on("end", () => {
|
|
setImmediate(() => {
|
|
console.log("end:false keeps the destination open", keep.writable, keep.writableEnded);
|
|
keep.end();
|
|
});
|
|
});
|
|
});
|
|
}
|