1use crate::host::{with_host, JsObj};
18use fusevm::Value;
19use indexmap::IndexMap;
20use std::cell::Cell;
21
22thread_local! {
25 static DEFAULT_HWM_BYTES: Cell<f64> = const { Cell::new(65536.0) };
26 static DEFAULT_HWM_OBJ: Cell<f64> = const { Cell::new(16.0) };
27}
28
29pub const CLASSES: &[&str] = &[
31 "Readable",
32 "Writable",
33 "Duplex",
34 "Transform",
35 "PassThrough",
36 "Stream",
37];
38
39pub const METHODS: &[&str] = &[
41 "finished",
42 "pipeline",
43 "addAbortSignal",
44 "destroy",
45 "isReadable",
46 "isWritable",
47 "isErrored",
48 "isDestroyed",
49 "isDisturbed",
50 "getDefaultHighWaterMark",
51 "setDefaultHighWaterMark",
52];
53
54pub fn is_class(name: &str) -> bool {
56 CLASSES.contains(&name)
57}
58
59pub fn constant(name: &str) -> Option<Value> {
62 if is_class(name) {
63 return Some(with_host(|h| h.alloc(JsObj::Builtin(name.to_string()))));
64 }
65
66 if name == "promises" {
68 return Some(with_host(|h| {
69 h.alloc(JsObj::Builtin("stream/promises".to_string()))
70 }));
71 }
72 None
73}
74
75pub fn construct(name: &str, args: &[Value]) -> Value {
78 let mut extra = IndexMap::new();
80 let queue = with_host(|h| h.new_array(Vec::new()));
81 extra.insert("@@queue".into(), queue);
82 if let Some(opts) = args.first() {
88 for (opt, key) in [
93 ("write", "@@writeImpl"),
94 ("final", "@@finalImpl"),
95 ("transform", "@@transformImpl"),
96 ("flush", "@@flushImpl"),
97 ] {
98 if let Some(f) = opt_callable(opts, opt) {
99 extra.insert(key.into(), f);
100 }
101 }
102 }
103 if matches!(name, "Readable" | "Duplex" | "Transform" | "PassThrough") {
111 extra.insert("readable".into(), Value::Bool(true));
112 }
113 if matches!(name, "Writable" | "Duplex" | "Transform" | "PassThrough") {
114 extra.insert("writable".into(), Value::Bool(true));
115 }
116 extra.insert("destroyed".into(), Value::Bool(false));
117 super::net::new_emitter_object(name, extra)
118}
119
120fn opt_callable(v: &Value, key: &str) -> Option<Value> {
122 let f = with_host(|h| match h.get(v) {
123 Some(JsObj::Object(m)) => m.get(key).cloned(),
124 _ => None,
125 })?;
126 with_host(|h| crate::host::is_callable(h, &f)).then_some(f)
127}
128
129fn run_write_impl(recv: &Value, chunk: &Value) -> Result<(), String> {
133 let Some(f) = hidden_prop(recv, "@@writeImpl") else {
134 return Ok(());
135 };
136 let enc = with_host(|h| h.new_str("utf8".to_string()));
137 let cb = with_host(|h| h.alloc(JsObj::Builtin("@@streamWriteCallback".into())));
141 crate::host::invoke(&f, vec![chunk.clone(), enc, cb], None)?;
142 Ok(())
143}
144
145fn hidden_prop(recv: &Value, key: &str) -> Option<Value> {
146 with_host(|h| match h.get(recv) {
147 Some(JsObj::Object(m)) => m.get(key).cloned(),
148 _ => None,
149 })
150}
151
152fn accept_chunk(recv: &Value, chunk: &Value) -> Result<(), String> {
159 if let Some(f) = hidden_prop(recv, "@@transformImpl") {
160 let enc = with_host(|h| h.new_str("utf8".to_string()));
161 let cb = match recv {
162 Value::Obj(i) => with_host(|h| h.alloc(JsObj::Builtin(format!("@@transformCb:{i}")))),
163 _ => Value::Undef,
164 };
165 crate::host::invoke(&f, vec![chunk.clone(), enc, cb], None)?;
166 return Ok(());
167 }
168 run_write_impl(recv, chunk)?;
169 emit_event(recv, "data", vec![chunk.clone()])?;
170 Ok(())
171}
172
173pub fn transform_callback(recv: &Value, args: &[Value]) -> Result<(), String> {
176 if let Some(err) = args.first().filter(|e| !with_host(|h| h.is_nullish(e))) {
177 emit_event(recv, "error", vec![err.clone()])?;
178 return Ok(());
179 }
180 if let Some(out) = args.get(1).filter(|c| !with_host(|h| h.is_nullish(c))) {
181 emit_event(recv, "data", vec![out.clone()])?;
182 }
183 Ok(())
184}
185
186pub const STATIC_METHODS: &[&str] = &["from", "isDisturbed"];
188
189pub fn static_call(cls: &str, method: &str, args: &[Value]) -> Option<Result<Value, String>> {
198 Some(match method {
199 "from" => Ok(from_iterable(cls, args)),
200 "isDisturbed" => Ok(Value::Bool(
201 hidden_prop(
202 &args.first().cloned().unwrap_or(Value::Undef),
203 "@@disturbed",
204 )
205 .is_some_and(|v| with_host(|h| h.truthy(&v))),
206 )),
207 _ => return None,
208 })
209}
210
211fn from_iterable(cls: &str, args: &[Value]) -> Value {
212 let src = args.first().cloned().unwrap_or(Value::Undef);
213 let whole = with_host(|h| h.as_str(&src).is_some())
217 || super::native_tag(&src).as_deref() == Some("Buffer");
218 let items = if whole {
219 vec![src.clone()]
220 } else {
221 crate::host::iter_all(&src).unwrap_or_default()
222 };
223 let stream = construct(
224 if cls == "Duplex" {
225 "Duplex"
226 } else {
227 "Readable"
228 },
229 &[],
230 );
231 if let Some(q) = queue_of(&stream) {
232 with_host(|h| {
233 if let Some(JsObj::Array(dst)) = h.get_mut(&q) {
234 dst.extend(items);
235 }
236 });
237 }
238 if let Value::Obj(i) = stream {
239 let thunk = with_host(|h| h.alloc(JsObj::Builtin(format!("@@streamFlush:{i}"))));
240 with_host(|h| h.queue_micro(thunk, Vec::new()));
241 }
242 stream
243}
244
245pub fn flush_from(recv: &Value) -> Result<(), String> {
247 let items = match queue_of(recv) {
248 Some(q) => with_host(|h| match h.get_mut(&q) {
249 Some(JsObj::Array(v)) => std::mem::take(v),
250 _ => Vec::new(),
251 }),
252 None => Vec::new(),
253 };
254 for item in items {
255 emit_event(recv, "data", vec![item])?;
256 }
257 emit_event(recv, "end", Vec::new())?;
258 Ok(())
259}
260
261pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
263 let s0 = || args.first().cloned().unwrap_or(Value::Undef);
264 Some(match method {
265 "getDefaultHighWaterMark" => Ok(get_default_hwm(args)),
266 "setDefaultHighWaterMark" => Ok(set_default_hwm(args)),
267 "isReadable" => Ok(Value::Bool(is_readable(&s0()))),
268 "isWritable" => Ok(Value::Bool(is_writable(&s0()))),
269 "isErrored" => Ok(Value::Bool(flag(&s0(), "@@errored"))),
270 "isDestroyed" => Ok(Value::Bool(flag(&s0(), "@@destroyed"))),
271 "isDisturbed" => Ok(Value::Bool(flag(&s0(), "@@disturbed"))),
272 "destroy" => Ok(destroy_stream(args)),
273 "finished" => Ok(finished(args)),
274 "pipeline" => pipeline(args),
275 "addAbortSignal" => Ok(add_abort_signal(args)),
276 _ => return None,
277 })
278}
279
280fn get_default_hwm(args: &[Value]) -> Value {
281 let obj = args
282 .first()
283 .map(|v| with_host(|h| h.truthy(v)))
284 .unwrap_or(false);
285 let n = if obj {
286 DEFAULT_HWM_OBJ.with(|c| c.get())
287 } else {
288 DEFAULT_HWM_BYTES.with(|c| c.get())
289 };
290 Value::Float(n)
291}
292
293fn set_default_hwm(args: &[Value]) -> Value {
294 let obj = args
295 .first()
296 .map(|v| with_host(|h| h.truthy(v)))
297 .unwrap_or(false);
298 let val = super::arg_num(args, 1);
299 if obj {
300 DEFAULT_HWM_OBJ.with(|c| c.set(val));
301 } else {
302 DEFAULT_HWM_BYTES.with(|c| c.set(val));
303 }
304 Value::Undef
305}
306
307fn tag_of(recv: &Value) -> Option<String> {
310 with_host(|h| match h.get(recv) {
311 Some(JsObj::Object(p)) => p.get("@@native").map(|v| h.str_of(v)),
312 _ => None,
313 })
314}
315
316fn flag(recv: &Value, key: &str) -> bool {
317 with_host(|h| match h.get(recv) {
318 Some(JsObj::Object(p)) => p.get(key).map(|v| h.truthy(v)).unwrap_or(false),
319 _ => false,
320 })
321}
322
323fn clear_side(recv: &Value, key: &str) {
326 let present =
327 with_host(|h| matches!(h.get(recv), Some(JsObj::Object(p)) if p.contains_key(key)));
328 if present {
329 set_flag(recv, key, Value::Bool(false));
330 }
331}
332
333fn set_flag(recv: &Value, key: &str, v: Value) {
334 with_host(|h| {
335 if let Some(JsObj::Object(p)) = h.get_mut(recv) {
336 p.insert(key.to_string(), v);
337 }
338 });
339}
340
341fn is_readable(s: &Value) -> bool {
342 let Some(t) = tag_of(s) else { return false };
343 matches!(
344 t.as_str(),
345 "Readable" | "Duplex" | "Transform" | "PassThrough"
346 ) && !flag(s, "@@destroyed")
347 && !flag(s, "@@ended")
348}
349
350fn is_writable(s: &Value) -> bool {
351 let Some(t) = tag_of(s) else { return false };
352 matches!(
353 t.as_str(),
354 "Writable" | "Duplex" | "Transform" | "PassThrough"
355 ) && !flag(s, "@@destroyed")
356 && !flag(s, "@@finished")
357}
358
359fn add_finished(recv: &Value, cb: Value) {
362 with_host(|h| {
363 let existing = match h.get(recv) {
364 Some(JsObj::Object(p)) => p.get("@@finished").cloned(),
365 _ => None,
366 };
367 let arr = match existing {
368 Some(a) if matches!(h.get(&a), Some(JsObj::Array(_))) => a,
369 _ => {
370 let a = h.new_array(Vec::new());
371 if let Some(JsObj::Object(p)) = h.get_mut(recv) {
372 p.insert("@@finished".into(), a.clone());
373 }
374 a
375 }
376 };
377 if let Some(JsObj::Array(items)) = h.get_mut(&arr) {
378 items.push(cb);
379 }
380 });
381}
382
383fn take_finished(recv: &Value) -> Vec<Value> {
384 with_host(|h| {
385 let arr = match h.get_mut(recv) {
386 Some(JsObj::Object(p)) => p.shift_remove("@@finished"),
387 _ => None,
388 };
389 match arr {
390 Some(av) => match h.get(&av) {
391 Some(JsObj::Array(items)) => items.clone(),
392 _ => Vec::new(),
393 },
394 None => Vec::new(),
395 }
396 })
397}
398
399fn emit_event(recv: &Value, name: &str, extra: Vec<Value>) -> Result<Value, String> {
402 let mut a = vec![with_host(|h| h.new_str(name))];
403 a.extend(extra.iter().cloned());
404 match name {
409 "end" => {
410 set_flag(recv, "@@ended", Value::Bool(true));
411 clear_side(recv, "readable");
412 }
413 "finish" => {
414 set_flag(recv, "@@finished", Value::Bool(true));
415 clear_side(recv, "writable");
416 }
417 "close" => {
418 set_flag(recv, "@@destroyed", Value::Bool(true));
419 set_flag(recv, "destroyed", Value::Bool(true));
420 clear_side(recv, "readable");
421 clear_side(recv, "writable");
422 }
423 "error" => set_flag(
424 recv,
425 "@@errored",
426 extra.first().cloned().unwrap_or(Value::Bool(true)),
427 ),
428 _ => {}
429 }
430 let r = super::events::instance_call(recv, "emit", a)?;
431 if matches!(name, "end" | "finish" | "close" | "error") {
432 let cbs = take_finished(recv);
433 let arg = if name == "error" {
434 extra.first().cloned().unwrap_or(Value::Undef)
435 } else {
436 Value::Undef
437 };
438 for cb in cbs {
439 crate::host::invoke(&cb, vec![arg.clone()], None)?;
440 }
441 }
442 Ok(r)
443}
444
445fn finished(args: &[Value]) -> Value {
452 let stream = args.first().cloned().unwrap_or(Value::Undef);
453 let cb = args
454 .iter()
455 .rev()
456 .find(|v| with_host(|h| crate::host::is_callable(h, v)))
457 .cloned()
458 .unwrap_or(Value::Undef);
459 if flag(&stream, "@@ended") || flag(&stream, "@@finished") || flag(&stream, "@@destroyed") {
460 let _ = crate::host::invoke(&cb, vec![Value::Undef], None);
461 } else {
462 add_finished(&stream, cb);
463 }
464 Value::Undef
465}
466
467fn pipeline(args: &[Value]) -> Result<Value, String> {
471 if args.is_empty() {
472 return Err(crate::host::invalid_arg_type(
475 "streams[stream.length - 1]",
476 "property",
477 "function",
478 &Value::Undef,
479 ));
480 }
481 let cb_idx = args
482 .iter()
483 .rposition(|v| with_host(|h| crate::host::is_callable(h, v)));
484 let (streams, cb) = match cb_idx {
485 Some(i) if i == args.len() - 1 => (&args[..i], Some(args[i].clone())),
486 _ => (args, None),
487 };
488 for w in streams.windows(2) {
489 crate::host::call_method(&w[0], "pipe", vec![w[1].clone()])?;
490 }
491 let last = streams.last().cloned().unwrap_or(Value::Undef);
492 if let Some(cb) = cb {
493 add_finished(&last, cb);
494 }
495 Ok(last)
496}
497
498fn destroy_stream(args: &[Value]) -> Value {
501 let stream = args.first().cloned().unwrap_or(Value::Undef);
502 if flag(&stream, "@@destroyed") {
503 return stream;
504 }
505 if let Some(e) = args.get(1).cloned() {
506 if !with_host(|h| h.is_nullish(&e)) {
507 let _ = emit_event(&stream, "error", vec![e]);
508 }
509 }
510 let _ = emit_event(&stream, "close", vec![]);
511 set_flag(&stream, "@@destroyed", Value::Bool(true));
512 stream
513}
514
515fn add_abort_signal(args: &[Value]) -> Value {
518 args.get(1).cloned().unwrap_or(Value::Undef)
519}
520
521pub fn instance_call(
524 tag: &str,
525 recv: &Value,
526 method: &str,
527 args: Vec<Value>,
528) -> Result<Value, String> {
529 let _ = tag;
530 if method == "emit" {
531 let name = args
532 .first()
533 .map(|v| with_host(|h| h.str_of(v)))
534 .unwrap_or_default();
535 let extra = args.get(1..).map(|s| s.to_vec()).unwrap_or_default();
536 return emit_event(recv, &name, extra);
537 }
538 if super::events::METHODS.contains(&method) {
543 return super::events::instance_call(recv, method, args);
544 }
545 match method {
546 "write" => {
547 let chunk = args.first().cloned().unwrap_or(Value::Undef);
548 accept_chunk(recv, &chunk)?;
549 Ok(Value::Bool(true))
550 }
551 "end" => {
552 if let Some(chunk) = args.first().filter(|v| !matches!(v, Value::Undef)) {
553 accept_chunk(recv, chunk)?;
554 }
555 emit_event(recv, "finish", vec![])?;
556 emit_event(recv, "end", vec![])?;
557 Ok(recv.clone())
558 }
559 "push" => {
560 let chunk = args.first().cloned().unwrap_or(Value::Undef);
561 if with_host(|h| h.is_nullish(&chunk)) {
562 emit_event(recv, "end", vec![])?;
563 return Ok(Value::Bool(false));
564 }
565 if let Some(q) = queue_of(recv) {
566 with_host(|h| {
567 if let Some(JsObj::Array(items)) = h.get_mut(&q) {
568 items.push(chunk.clone());
569 }
570 });
571 }
572 emit_event(recv, "data", vec![chunk])?;
573 Ok(Value::Bool(true))
574 }
575 "read" => {
576 set_flag(recv, "@@disturbed", Value::Bool(true));
577 if let Some(q) = queue_of(recv) {
578 let next = with_host(|h| match h.get_mut(&q) {
579 Some(JsObj::Array(items)) if !items.is_empty() => Some(items.remove(0)),
580 _ => None,
581 });
582 if let Some(v) = next {
583 return Ok(v);
584 }
585 }
586 Ok(with_host(|h| h.null()))
587 }
588 "pipe" => {
589 set_flag(recv, "@@disturbed", Value::Bool(true));
590 let dest = args.first().cloned().unwrap_or(Value::Undef);
591 if let Some(q) = queue_of(recv) {
592 let items = with_host(|h| match h.get(&q) {
593 Some(JsObj::Array(items)) => items.clone(),
594 _ => Vec::new(),
595 });
596 for chunk in items {
597 crate::host::call_method(&dest, "write", vec![chunk])?;
598 }
599 }
600 Ok(dest)
601 }
602 "destroy" => {
603 if !flag(recv, "@@destroyed") {
604 if let Some(e) = args.first().filter(|v| !matches!(v, Value::Undef)) {
605 let _ = emit_event(recv, "error", vec![e.clone()]);
606 }
607 let _ = emit_event(recv, "close", vec![]);
608 set_flag(recv, "@@destroyed", Value::Bool(true));
609 }
610 Ok(recv.clone())
611 }
612 "resume" => {
613 set_flag(recv, "@@disturbed", Value::Bool(true));
614 Ok(recv.clone())
615 }
616 "setEncoding" | "pause" | "cork" | "uncork" => Ok(recv.clone()),
617 _ => Err(crate::host::type_error(&format!(
618 "stream.{method} is not a function"
619 ))),
620 }
621}
622
623fn queue_of(recv: &Value) -> Option<Value> {
624 with_host(|h| match h.get(recv) {
625 Some(JsObj::Object(p)) => p.get("@@queue").cloned(),
626 _ => None,
627 })
628}