Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -733,8 +733,8 @@ pub(super) const NET_EVENTS_ROWS: &[NativeModSig] = &[
has_receiver: false,
method: "from",
class_filter: None,
runtime: "js_node_stream_readable_from",
args: &[NA_F64],
runtime: "js_node_stream_readable_from_options",
args: &[NA_F64, NA_F64],
ret: NR_F64,
},
// #1534: static introspection helpers `isDisturbed` and
Expand Down
5 changes: 5 additions & 0 deletions crates/perry-codegen/src/runtime_decls/stdlib_ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,11 @@ pub fn declare_stdlib_ffi(module: &mut LlModule) {
module.declare_function("js_node_stream_transform_new", DOUBLE, &[DOUBLE]);
module.declare_function("js_node_stream_passthrough_new", DOUBLE, &[DOUBLE]);
module.declare_function("js_node_stream_readable_from", DOUBLE, &[DOUBLE]);
module.declare_function(
"js_node_stream_readable_from_options",
DOUBLE,
&[DOUBLE, DOUBLE],
);
// #1534: static introspection helpers reflecting tracked stream state.
module.declare_function("js_node_stream_is_disturbed", DOUBLE, &[DOUBLE]);
module.declare_function("js_node_stream_is_errored", DOUBLE, &[DOUBLE]);
Expand Down
25 changes: 24 additions & 1 deletion crates/perry-runtime/src/node_stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3053,6 +3053,24 @@ fn normalize_readable_from_input(iterable: f64) -> f64 {
box_pointer(arr as *const u8)
}

fn readable_from_options(opts: f64) -> f64 {
let merged = crate::object::js_object_alloc(0, 2);
let object_mode = !get_hidden_value(opts, hidden_key(b"objectMode"))
.is_some_and(|v| v.to_bits() == TAG_FALSE);
set_hidden_value(
box_pointer(merged as *const u8),
hidden_key(b"objectMode"),
f64::from_bits(if object_mode { TAG_TRUE } else { TAG_FALSE }),
);
let hwm = opt_number(opts, b"highWaterMark").unwrap_or(1.0);
set_hidden_value(
box_pointer(merged as *const u8),
hidden_key(b"highWaterMark"),
hwm,
);
box_pointer(merged as *const u8)
}

fn append_string_bytes(value: f64, out: &mut Vec<u8>) {
let ptr = crate::value::js_get_string_pointer_unified(value) as *const crate::StringHeader;
append_string_ptr_bytes(ptr, out);
Expand Down Expand Up @@ -3800,12 +3818,17 @@ pub extern "C" fn js_node_stream_passthrough_new(opts: f64) -> f64 {
/// `node:stream/consumers` can drain the current stub stream surface.
#[no_mangle]
pub extern "C" fn js_node_stream_readable_from(iterable: f64) -> f64 {
js_node_stream_readable_from_options(iterable, f64::from_bits(TAG_UNDEFINED))
}

#[no_mangle]
pub extern "C" fn js_node_stream_readable_from_options(iterable: f64, opts: f64) -> f64 {
if matches!(iterable.to_bits(), TAG_NULL | TAG_UNDEFINED)
|| is_non_iterable_primitive_for_readable_from(iterable)
{
throw_readable_from_invalid_iterable();
}
let readable = js_node_stream_readable_new(f64::from_bits(TAG_UNDEFINED));
let readable = js_node_stream_readable_new(readable_from_options(opts));
let raw = raw_ptr_from_value(readable);
if raw >= 0x10000 {
let chunks = normalize_readable_from_input(iterable);
Expand Down
3 changes: 3 additions & 0 deletions crates/perry-runtime/src/node_stream_keepalive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ static KEEP_NS_PASSTHROUGH_NEW: extern "C" fn(f64) -> f64 = super::js_node_strea
#[used]
static KEEP_NS_READABLE_FROM: extern "C" fn(f64) -> f64 = super::js_node_stream_readable_from;
#[used]
static KEEP_NS_READABLE_FROM_OPTIONS: extern "C" fn(f64, f64) -> f64 =
super::js_node_stream_readable_from_options;
#[used]
static KEEP_NS_IS_DISTURBED: extern "C" fn(f64) -> f64 = super::js_node_stream_is_disturbed;
#[used]
static KEEP_NS_IS_ERRORED: extern "C" fn(f64) -> f64 = super::js_node_stream_is_errored;
Expand Down
29 changes: 29 additions & 0 deletions crates/perry-runtime/src/node_stream_tests_extra.rs
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,35 @@ fn stream_object_mode_fields_reflect_defaults_and_options() {
);
}

#[test]
fn readable_from_uses_node_object_mode_and_high_water_mark_defaults() {
let mut default_chunks = crate::array::js_array_alloc(1);
default_chunks = crate::array::js_array_push_f64(default_chunks, string_value("a"));
let default_readable = js_node_stream_readable_from(box_pointer(default_chunks as *const u8));
let default_handle = raw_ptr_from_value(default_readable) as i64;
assert_eq!(
js_node_stream_method_readable_object_mode(default_handle).to_bits(),
TAG_TRUE
);
assert_eq!(js_node_stream_method_readable_hwm(default_handle), 1.0);

let mut byte_chunks = crate::array::js_array_alloc(1);
byte_chunks = crate::array::js_array_push_f64(byte_chunks, string_value("b"));
let opts = crate::object::js_object_alloc(0, 2);
js_object_set_field_by_name(opts, hidden_key(b"objectMode"), f64::from_bits(TAG_FALSE));
js_object_set_field_by_name(opts, hidden_key(b"highWaterMark"), 1.0);
let byte_readable = js_node_stream_readable_from_options(
box_pointer(byte_chunks as *const u8),
box_pointer(opts as *const u8),
);
let byte_handle = raw_ptr_from_value(byte_readable) as i64;
assert_eq!(
js_node_stream_method_readable_object_mode(byte_handle).to_bits(),
TAG_FALSE
);
assert_eq!(js_node_stream_method_readable_hwm(byte_handle), 1.0);
}

#[test]
fn writable_corked_counter_tracks_cork_balance() {
let stream = js_node_stream_writable_new(f64::from_bits(TAG_UNDEFINED));
Expand Down
12 changes: 0 additions & 12 deletions test-parity/known_failures.json
Original file line number Diff line number Diff line change
Expand Up @@ -500,12 +500,6 @@
"category": "bug-open",
"reason": "node:stream: pipe() called twice with same destination duplicates writes (should be no-op for second call). Flips to PASS when #1532 lands."
},
"node-suite/stream/readable/from-with-options": {
"issue": "1532",
"added": "2026-05-24",
"category": "bug-open",
"reason": "node:stream: Readable.from(iter, options) \u2014 2nd-arg stream options (objectMode/highWaterMark) ignored. Flips to PASS when #1532 lands."
},
"node-suite/stream/pipe/end-false-option": {
"issue": "1532",
"added": "2026-05-24",
Expand Down Expand Up @@ -1082,12 +1076,6 @@
"category": "bug-open",
"reason": "node:stream: compose(string) \u2014 does not throw TypeError. Flips to PASS when #1531 lands."
},
"node-suite/stream/readable/static-from-with-options": {
"issue": "1532",
"added": "2026-05-24",
"category": "bug-open",
"reason": "node:stream: Readable.from(iter, opts) \u2014 objectMode/hwm 2nd-arg not applied. Flips to PASS when #1532 lands."
},
"node-suite/stream/compose/two-stage-error-stops": {
"issue": "1531",
"added": "2026-05-24",
Expand Down