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 Some(with_host(|h| h.alloc(JsObj::Builtin(name.to_string()))))
64 } else {
65 None
66 }
67}
68
69pub fn construct(name: &str, args: &[Value]) -> Value {
72 let mut extra = IndexMap::new();
74 let queue = with_host(|h| h.new_array(Vec::new()));
75 extra.insert("@@queue".into(), queue);
76 if let Some(opts) = args.first() {
82 for (opt, key) in [("write", "@@writeImpl"), ("final", "@@finalImpl")] {
83 if let Some(f) = opt_callable(opts, opt) {
84 extra.insert(key.into(), f);
85 }
86 }
87 }
88 super::net::new_emitter_object(name, extra)
89}
90
91fn opt_callable(v: &Value, key: &str) -> Option<Value> {
93 let f = with_host(|h| match h.get(v) {
94 Some(JsObj::Object(m)) => m.get(key).cloned(),
95 _ => None,
96 })?;
97 with_host(|h| crate::host::is_callable(h, &f)).then_some(f)
98}
99
100fn run_write_impl(recv: &Value, chunk: &Value) -> Result<(), String> {
104 let Some(f) = hidden_prop(recv, "@@writeImpl") else {
105 return Ok(());
106 };
107 let enc = with_host(|h| h.new_str("utf8".to_string()));
108 let cb = with_host(|h| h.alloc(JsObj::Builtin("@@streamWriteCallback".into())));
112 crate::host::invoke(&f, vec![chunk.clone(), enc, cb], None)?;
113 Ok(())
114}
115
116fn hidden_prop(recv: &Value, key: &str) -> Option<Value> {
117 with_host(|h| match h.get(recv) {
118 Some(JsObj::Object(m)) => m.get(key).cloned(),
119 _ => None,
120 })
121}
122
123pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
125 let s0 = || args.first().cloned().unwrap_or(Value::Undef);
126 Some(match method {
127 "getDefaultHighWaterMark" => Ok(get_default_hwm(args)),
128 "setDefaultHighWaterMark" => Ok(set_default_hwm(args)),
129 "isReadable" => Ok(Value::Bool(is_readable(&s0()))),
130 "isWritable" => Ok(Value::Bool(is_writable(&s0()))),
131 "isErrored" => Ok(Value::Bool(flag(&s0(), "@@errored"))),
132 "isDestroyed" => Ok(Value::Bool(flag(&s0(), "@@destroyed"))),
133 "isDisturbed" => Ok(Value::Bool(flag(&s0(), "@@disturbed"))),
134 "destroy" => Ok(destroy_stream(args)),
135 "finished" => Ok(finished(args)),
136 "pipeline" => pipeline(args),
137 "addAbortSignal" => Ok(add_abort_signal(args)),
138 _ => return None,
139 })
140}
141
142fn get_default_hwm(args: &[Value]) -> Value {
143 let obj = args
144 .first()
145 .map(|v| with_host(|h| h.truthy(v)))
146 .unwrap_or(false);
147 let n = if obj {
148 DEFAULT_HWM_OBJ.with(|c| c.get())
149 } else {
150 DEFAULT_HWM_BYTES.with(|c| c.get())
151 };
152 Value::Float(n)
153}
154
155fn set_default_hwm(args: &[Value]) -> Value {
156 let obj = args
157 .first()
158 .map(|v| with_host(|h| h.truthy(v)))
159 .unwrap_or(false);
160 let val = super::arg_num(args, 1);
161 if obj {
162 DEFAULT_HWM_OBJ.with(|c| c.set(val));
163 } else {
164 DEFAULT_HWM_BYTES.with(|c| c.set(val));
165 }
166 Value::Undef
167}
168
169fn tag_of(recv: &Value) -> Option<String> {
172 with_host(|h| match h.get(recv) {
173 Some(JsObj::Object(p)) => p.get("@@native").map(|v| h.str_of(v)),
174 _ => None,
175 })
176}
177
178fn flag(recv: &Value, key: &str) -> bool {
179 with_host(|h| match h.get(recv) {
180 Some(JsObj::Object(p)) => p.get(key).map(|v| h.truthy(v)).unwrap_or(false),
181 _ => false,
182 })
183}
184
185fn set_flag(recv: &Value, key: &str, v: Value) {
186 with_host(|h| {
187 if let Some(JsObj::Object(p)) = h.get_mut(recv) {
188 p.insert(key.to_string(), v);
189 }
190 });
191}
192
193fn is_readable(s: &Value) -> bool {
194 let Some(t) = tag_of(s) else { return false };
195 matches!(
196 t.as_str(),
197 "Readable" | "Duplex" | "Transform" | "PassThrough"
198 ) && !flag(s, "@@destroyed")
199 && !flag(s, "@@ended")
200}
201
202fn is_writable(s: &Value) -> bool {
203 let Some(t) = tag_of(s) else { return false };
204 matches!(
205 t.as_str(),
206 "Writable" | "Duplex" | "Transform" | "PassThrough"
207 ) && !flag(s, "@@destroyed")
208 && !flag(s, "@@finished")
209}
210
211fn add_finished(recv: &Value, cb: Value) {
214 with_host(|h| {
215 let existing = match h.get(recv) {
216 Some(JsObj::Object(p)) => p.get("@@finished").cloned(),
217 _ => None,
218 };
219 let arr = match existing {
220 Some(a) if matches!(h.get(&a), Some(JsObj::Array(_))) => a,
221 _ => {
222 let a = h.new_array(Vec::new());
223 if let Some(JsObj::Object(p)) = h.get_mut(recv) {
224 p.insert("@@finished".into(), a.clone());
225 }
226 a
227 }
228 };
229 if let Some(JsObj::Array(items)) = h.get_mut(&arr) {
230 items.push(cb);
231 }
232 });
233}
234
235fn take_finished(recv: &Value) -> Vec<Value> {
236 with_host(|h| {
237 let arr = match h.get_mut(recv) {
238 Some(JsObj::Object(p)) => p.shift_remove("@@finished"),
239 _ => None,
240 };
241 match arr {
242 Some(av) => match h.get(&av) {
243 Some(JsObj::Array(items)) => items.clone(),
244 _ => Vec::new(),
245 },
246 None => Vec::new(),
247 }
248 })
249}
250
251fn emit_event(recv: &Value, name: &str, extra: Vec<Value>) -> Result<Value, String> {
254 let mut a = vec![with_host(|h| h.new_str(name))];
255 a.extend(extra.iter().cloned());
256 let r = super::events::instance_call(recv, "emit", a)?;
257 match name {
258 "end" => set_flag(recv, "@@ended", Value::Bool(true)),
259 "finish" => set_flag(recv, "@@finished", Value::Bool(true)),
260 "close" => set_flag(recv, "@@destroyed", Value::Bool(true)),
261 "error" => set_flag(
262 recv,
263 "@@errored",
264 extra.first().cloned().unwrap_or(Value::Bool(true)),
265 ),
266 _ => {}
267 }
268 if matches!(name, "end" | "finish" | "close" | "error") {
269 let cbs = take_finished(recv);
270 let arg = if name == "error" {
271 extra.first().cloned().unwrap_or(Value::Undef)
272 } else {
273 Value::Undef
274 };
275 for cb in cbs {
276 crate::host::invoke(&cb, vec![arg.clone()], None)?;
277 }
278 }
279 Ok(r)
280}
281
282fn finished(args: &[Value]) -> Value {
289 let stream = args.first().cloned().unwrap_or(Value::Undef);
290 let cb = args
291 .iter()
292 .rev()
293 .find(|v| with_host(|h| crate::host::is_callable(h, v)))
294 .cloned()
295 .unwrap_or(Value::Undef);
296 if flag(&stream, "@@ended") || flag(&stream, "@@finished") || flag(&stream, "@@destroyed") {
297 let _ = crate::host::invoke(&cb, vec![Value::Undef], None);
298 } else {
299 add_finished(&stream, cb);
300 }
301 Value::Undef
302}
303
304fn pipeline(args: &[Value]) -> Result<Value, String> {
308 if args.is_empty() {
309 return Err(crate::host::invalid_arg_type(
312 "streams[stream.length - 1]",
313 "property",
314 "function",
315 &Value::Undef,
316 ));
317 }
318 let cb_idx = args
319 .iter()
320 .rposition(|v| with_host(|h| crate::host::is_callable(h, v)));
321 let (streams, cb) = match cb_idx {
322 Some(i) if i == args.len() - 1 => (&args[..i], Some(args[i].clone())),
323 _ => (args, None),
324 };
325 for w in streams.windows(2) {
326 crate::host::call_method(&w[0], "pipe", vec![w[1].clone()])?;
327 }
328 let last = streams.last().cloned().unwrap_or(Value::Undef);
329 if let Some(cb) = cb {
330 add_finished(&last, cb);
331 }
332 Ok(last)
333}
334
335fn destroy_stream(args: &[Value]) -> Value {
338 let stream = args.first().cloned().unwrap_or(Value::Undef);
339 if flag(&stream, "@@destroyed") {
340 return stream;
341 }
342 if let Some(e) = args.get(1).cloned() {
343 if !with_host(|h| h.is_nullish(&e)) {
344 let _ = emit_event(&stream, "error", vec![e]);
345 }
346 }
347 let _ = emit_event(&stream, "close", vec![]);
348 set_flag(&stream, "@@destroyed", Value::Bool(true));
349 stream
350}
351
352fn add_abort_signal(args: &[Value]) -> Value {
355 args.get(1).cloned().unwrap_or(Value::Undef)
356}
357
358pub fn instance_call(
361 tag: &str,
362 recv: &Value,
363 method: &str,
364 args: Vec<Value>,
365) -> Result<Value, String> {
366 let _ = tag;
367 if method == "emit" {
368 let name = args
369 .first()
370 .map(|v| with_host(|h| h.str_of(v)))
371 .unwrap_or_default();
372 let extra = args.get(1..).map(|s| s.to_vec()).unwrap_or_default();
373 return emit_event(recv, &name, extra);
374 }
375 if super::events::METHODS.contains(&method) {
380 return super::events::instance_call(recv, method, args);
381 }
382 match method {
383 "write" => {
384 let chunk = args.first().cloned().unwrap_or(Value::Undef);
385 run_write_impl(recv, &chunk)?;
386 emit_event(recv, "data", vec![chunk])?;
387 Ok(Value::Bool(true))
388 }
389 "end" => {
390 if let Some(chunk) = args.first().filter(|v| !matches!(v, Value::Undef)) {
391 run_write_impl(recv, chunk)?;
392 emit_event(recv, "data", vec![chunk.clone()])?;
393 }
394 emit_event(recv, "finish", vec![])?;
395 emit_event(recv, "end", vec![])?;
396 Ok(recv.clone())
397 }
398 "push" => {
399 let chunk = args.first().cloned().unwrap_or(Value::Undef);
400 if with_host(|h| h.is_nullish(&chunk)) {
401 emit_event(recv, "end", vec![])?;
402 return Ok(Value::Bool(false));
403 }
404 if let Some(q) = queue_of(recv) {
405 with_host(|h| {
406 if let Some(JsObj::Array(items)) = h.get_mut(&q) {
407 items.push(chunk.clone());
408 }
409 });
410 }
411 emit_event(recv, "data", vec![chunk])?;
412 Ok(Value::Bool(true))
413 }
414 "read" => {
415 set_flag(recv, "@@disturbed", Value::Bool(true));
416 if let Some(q) = queue_of(recv) {
417 let next = with_host(|h| match h.get_mut(&q) {
418 Some(JsObj::Array(items)) if !items.is_empty() => Some(items.remove(0)),
419 _ => None,
420 });
421 if let Some(v) = next {
422 return Ok(v);
423 }
424 }
425 Ok(with_host(|h| h.null()))
426 }
427 "pipe" => {
428 set_flag(recv, "@@disturbed", Value::Bool(true));
429 let dest = args.first().cloned().unwrap_or(Value::Undef);
430 if let Some(q) = queue_of(recv) {
431 let items = with_host(|h| match h.get(&q) {
432 Some(JsObj::Array(items)) => items.clone(),
433 _ => Vec::new(),
434 });
435 for chunk in items {
436 crate::host::call_method(&dest, "write", vec![chunk])?;
437 }
438 }
439 Ok(dest)
440 }
441 "destroy" => {
442 if !flag(recv, "@@destroyed") {
443 if let Some(e) = args.first().filter(|v| !matches!(v, Value::Undef)) {
444 let _ = emit_event(recv, "error", vec![e.clone()]);
445 }
446 let _ = emit_event(recv, "close", vec![]);
447 set_flag(recv, "@@destroyed", Value::Bool(true));
448 }
449 Ok(recv.clone())
450 }
451 "resume" => {
452 set_flag(recv, "@@disturbed", Value::Bool(true));
453 Ok(recv.clone())
454 }
455 "setEncoding" | "pause" | "cork" | "uncork" => Ok(recv.clone()),
456 _ => Err(crate::host::type_error(&format!(
457 "stream.{method} is not a function"
458 ))),
459 }
460}
461
462fn queue_of(recv: &Value) -> Option<Value> {
463 with_host(|h| match h.get(recv) {
464 Some(JsObj::Object(p)) => p.get("@@queue").cloned(),
465 _ => None,
466 })
467}