mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-27 06:43:19 +08:00
Bundled npm code (from2, bl, tar-stream, yazl, multistream) builds its streams as ES5 constructor functions: util.inherits(Ctor, Readable) and Readable.call(this, opts), with _read/_write/_transform on the prototype. Readable.call(this) threw SC2020. A new runtime unit, scr_dyn_builtin_stream.c (feature dynBuiltinStream), gives the untyped stream constructors their stream half. Base.call(this, opts) creates a runtime stream over the receiver and keeps it in a hidden slot; the runtime calls the receiver's underscore methods through its property lookup; the stream prototypes carry Node's methods and accessors over the hidden stream; and EventEmitter's methods register listeners with the runtime stream's emitter, so runtime events and pipes reach JavaScript listeners with the receiver as this. _readableState/_writableState are live views. Stream class values now come from dyn.builtinStreamClass, so only programs that use them link the stream half. Typed code sees these objects as their runtime streams: checked casts to the stream classes take them, readable.pipe() into an untyped destination pipes into them, and runtime streams crossing into JavaScript answer the constructor object. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
122 lines
3.9 KiB
JavaScript
122 lines
3.9 KiB
JavaScript
// Readable streams built by constructor functions, the from2 pattern:
|
|
// util.inherits(Ctor, Readable), Readable.call(this, opts) and _read on
|
|
// the prototype.
|
|
const { Readable } = require("stream");
|
|
const util = require("util");
|
|
|
|
function Source(list, opts) {
|
|
if (!(this instanceof Source)) return new Source(list, opts);
|
|
this._list = list.slice();
|
|
// Before Readable.call there is no state: Node's setter ignores this.
|
|
this.destroyed = false;
|
|
Readable.call(this, opts || {});
|
|
this._reads = 0;
|
|
console.log("hwm", this._readableState.highWaterMark, "objectMode", this._readableState.objectMode);
|
|
}
|
|
util.inherits(Source, Readable);
|
|
Source.prototype._read = function (size) {
|
|
this._reads++;
|
|
const item = this._list.shift();
|
|
this.push(item === undefined ? null : Buffer.from(item));
|
|
};
|
|
|
|
const s = Source(["alpha", "beta", "gamma"]);
|
|
console.log("instanceof", s instanceof Source, s instanceof Readable, Source.super_ === Readable);
|
|
console.log("readable", s.readable, "ended", s.readableEnded, "destroyed", s.destroyed);
|
|
console.log("flowing", s.readableFlowing, s._readableState.flowing);
|
|
s.on("data", function (chunk) {
|
|
console.log("data", this === s, Buffer.isBuffer(chunk), chunk.toString());
|
|
});
|
|
s.on("end", function () {
|
|
console.log("end", this === s, s.readableEnded, s._readableState.ended, s._reads);
|
|
});
|
|
s.on("close", () => {
|
|
console.log("close", s.destroyed, s.closed);
|
|
paused();
|
|
});
|
|
console.log("sync after on('data')", s.readableFlowing);
|
|
|
|
// from2's callback-driven _read, asynchronous pushes and a small
|
|
// highWaterMark.
|
|
function From(read, opts) {
|
|
if (!(this instanceof From)) return new From(read, opts);
|
|
this._reading = false;
|
|
this._callback = check;
|
|
this.destroyed = false;
|
|
Readable.call(this, opts);
|
|
const self = this;
|
|
const hwm = this._readableState.highWaterMark;
|
|
function check(err, data) {
|
|
if (self.destroyed) return;
|
|
if (err) return self.destroy(err);
|
|
if (data === null) return self.push(null);
|
|
self._reading = false;
|
|
if (self.push(data)) self._read(hwm);
|
|
}
|
|
this._from = read;
|
|
}
|
|
util.inherits(From, Readable);
|
|
From.prototype._read = function (size) {
|
|
if (this._reading || this.destroyed) return;
|
|
this._reading = true;
|
|
this._from(size, this._callback);
|
|
};
|
|
From.prototype.destroy = function (err) {
|
|
if (this.destroyed) return;
|
|
this.destroyed = true;
|
|
const self = this;
|
|
process.nextTick(function () {
|
|
if (err) self.emit("error", err);
|
|
self.emit("close");
|
|
});
|
|
};
|
|
|
|
function paused() {
|
|
const parts = ["one ", "two ", "three"];
|
|
const r = new From(function (size, next) {
|
|
setImmediate(() => next(null, parts.length ? parts.shift() : null));
|
|
}, { highWaterMark: 4 });
|
|
console.log("from hwm", r._readableState.highWaterMark, r.readableHighWaterMark);
|
|
r.setEncoding("utf8");
|
|
let text = "";
|
|
r.on("readable", () => {
|
|
let chunk;
|
|
while ((chunk = r.read()) !== null) text += chunk;
|
|
});
|
|
r.on("end", () => {
|
|
console.log("paused text", JSON.stringify(text), typeof text);
|
|
// From() without new runs outside the listener, where `this` is unset.
|
|
setImmediate(failing);
|
|
});
|
|
}
|
|
|
|
function failing() {
|
|
let calls = 0;
|
|
const r = From(function (size, next) {
|
|
calls++;
|
|
if (calls === 2) return next(new Error("source failed"));
|
|
next(null, "chunk" + calls);
|
|
});
|
|
r.on("data", (d) => console.log("failing data", String(d)));
|
|
r.on("error", (e) => console.log("failing error", e.message, r.destroyed));
|
|
r.on("close", () => {
|
|
console.log("failing close", calls);
|
|
unshifted();
|
|
});
|
|
}
|
|
|
|
function unshifted() {
|
|
const r = new Source(["xyz"]);
|
|
r.once("readable", () => {
|
|
const first = r.read(1);
|
|
console.log("read(1)", String(first), r.readableLength);
|
|
r.unshift(first);
|
|
console.log("after unshift", r.readableLength);
|
|
r.pause();
|
|
console.log("isPaused", r.isPaused());
|
|
r.on("data", (d) => console.log("rest", String(d)));
|
|
r.resume();
|
|
});
|
|
r.on("end", () => console.log("unshifted end"));
|
|
}
|