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) -> 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 super::net::new_emitter_object(name, extra)
77}
78
79pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
81 let s0 = || args.first().cloned().unwrap_or(Value::Undef);
82 Some(match method {
83 "getDefaultHighWaterMark" => Ok(get_default_hwm(args)),
84 "setDefaultHighWaterMark" => Ok(set_default_hwm(args)),
85 "isReadable" => Ok(Value::Bool(is_readable(&s0()))),
86 "isWritable" => Ok(Value::Bool(is_writable(&s0()))),
87 "isErrored" => Ok(Value::Bool(flag(&s0(), "@@errored"))),
88 "isDestroyed" => Ok(Value::Bool(flag(&s0(), "@@destroyed"))),
89 "isDisturbed" => Ok(Value::Bool(flag(&s0(), "@@disturbed"))),
90 "destroy" => Ok(destroy_stream(args)),
91 "finished" => Ok(finished(args)),
92 "pipeline" => pipeline(args),
93 "addAbortSignal" => Ok(add_abort_signal(args)),
94 _ => return None,
95 })
96}
97
98fn get_default_hwm(args: &[Value]) -> Value {
99 let obj = args
100 .first()
101 .map(|v| with_host(|h| h.truthy(v)))
102 .unwrap_or(false);
103 let n = if obj {
104 DEFAULT_HWM_OBJ.with(|c| c.get())
105 } else {
106 DEFAULT_HWM_BYTES.with(|c| c.get())
107 };
108 Value::Float(n)
109}
110
111fn set_default_hwm(args: &[Value]) -> Value {
112 let obj = args
113 .first()
114 .map(|v| with_host(|h| h.truthy(v)))
115 .unwrap_or(false);
116 let val = super::arg_num(args, 1);
117 if obj {
118 DEFAULT_HWM_OBJ.with(|c| c.set(val));
119 } else {
120 DEFAULT_HWM_BYTES.with(|c| c.set(val));
121 }
122 Value::Undef
123}
124
125fn tag_of(recv: &Value) -> Option<String> {
128 with_host(|h| match h.get(recv) {
129 Some(JsObj::Object(p)) => p.get("@@native").map(|v| h.str_of(v)),
130 _ => None,
131 })
132}
133
134fn flag(recv: &Value, key: &str) -> bool {
135 with_host(|h| match h.get(recv) {
136 Some(JsObj::Object(p)) => p.get(key).map(|v| h.truthy(v)).unwrap_or(false),
137 _ => false,
138 })
139}
140
141fn set_flag(recv: &Value, key: &str, v: Value) {
142 with_host(|h| {
143 if let Some(JsObj::Object(p)) = h.get_mut(recv) {
144 p.insert(key.to_string(), v);
145 }
146 });
147}
148
149fn is_readable(s: &Value) -> bool {
150 let Some(t) = tag_of(s) else { return false };
151 matches!(
152 t.as_str(),
153 "Readable" | "Duplex" | "Transform" | "PassThrough"
154 ) && !flag(s, "@@destroyed")
155 && !flag(s, "@@ended")
156}
157
158fn is_writable(s: &Value) -> bool {
159 let Some(t) = tag_of(s) else { return false };
160 matches!(
161 t.as_str(),
162 "Writable" | "Duplex" | "Transform" | "PassThrough"
163 ) && !flag(s, "@@destroyed")
164 && !flag(s, "@@finished")
165}
166
167fn add_finished(recv: &Value, cb: Value) {
170 with_host(|h| {
171 let existing = match h.get(recv) {
172 Some(JsObj::Object(p)) => p.get("@@finished").cloned(),
173 _ => None,
174 };
175 let arr = match existing {
176 Some(a) if matches!(h.get(&a), Some(JsObj::Array(_))) => a,
177 _ => {
178 let a = h.new_array(Vec::new());
179 if let Some(JsObj::Object(p)) = h.get_mut(recv) {
180 p.insert("@@finished".into(), a.clone());
181 }
182 a
183 }
184 };
185 if let Some(JsObj::Array(items)) = h.get_mut(&arr) {
186 items.push(cb);
187 }
188 });
189}
190
191fn take_finished(recv: &Value) -> Vec<Value> {
192 with_host(|h| {
193 let arr = match h.get_mut(recv) {
194 Some(JsObj::Object(p)) => p.shift_remove("@@finished"),
195 _ => None,
196 };
197 match arr {
198 Some(av) => match h.get(&av) {
199 Some(JsObj::Array(items)) => items.clone(),
200 _ => Vec::new(),
201 },
202 None => Vec::new(),
203 }
204 })
205}
206
207fn emit_event(recv: &Value, name: &str, extra: Vec<Value>) -> Result<Value, String> {
210 let mut a = vec![with_host(|h| h.new_str(name))];
211 a.extend(extra.iter().cloned());
212 let r = super::events::instance_call(recv, "emit", a)?;
213 match name {
214 "end" => set_flag(recv, "@@ended", Value::Bool(true)),
215 "finish" => set_flag(recv, "@@finished", Value::Bool(true)),
216 "close" => set_flag(recv, "@@destroyed", Value::Bool(true)),
217 "error" => set_flag(
218 recv,
219 "@@errored",
220 extra.first().cloned().unwrap_or(Value::Bool(true)),
221 ),
222 _ => {}
223 }
224 if matches!(name, "end" | "finish" | "close" | "error") {
225 let cbs = take_finished(recv);
226 let arg = if name == "error" {
227 extra.first().cloned().unwrap_or(Value::Undef)
228 } else {
229 Value::Undef
230 };
231 for cb in cbs {
232 crate::host::invoke(&cb, vec![arg.clone()], None)?;
233 }
234 }
235 Ok(r)
236}
237
238fn finished(args: &[Value]) -> Value {
245 let stream = args.first().cloned().unwrap_or(Value::Undef);
246 let cb = args
247 .iter()
248 .rev()
249 .find(|v| with_host(|h| crate::host::is_callable(h, v)))
250 .cloned()
251 .unwrap_or(Value::Undef);
252 if flag(&stream, "@@ended") || flag(&stream, "@@finished") || flag(&stream, "@@destroyed") {
253 let _ = crate::host::invoke(&cb, vec![Value::Undef], None);
254 } else {
255 add_finished(&stream, cb);
256 }
257 Value::Undef
258}
259
260fn pipeline(args: &[Value]) -> Result<Value, String> {
264 if args.is_empty() {
265 return Err(crate::host::type_error(
266 "pipeline requires at least one stream",
267 ));
268 }
269 let cb_idx = args
270 .iter()
271 .rposition(|v| with_host(|h| crate::host::is_callable(h, v)));
272 let (streams, cb) = match cb_idx {
273 Some(i) if i == args.len() - 1 => (&args[..i], Some(args[i].clone())),
274 _ => (args, None),
275 };
276 for w in streams.windows(2) {
277 crate::host::call_method(&w[0], "pipe", vec![w[1].clone()])?;
278 }
279 let last = streams.last().cloned().unwrap_or(Value::Undef);
280 if let Some(cb) = cb {
281 add_finished(&last, cb);
282 }
283 Ok(last)
284}
285
286fn destroy_stream(args: &[Value]) -> Value {
289 let stream = args.first().cloned().unwrap_or(Value::Undef);
290 if flag(&stream, "@@destroyed") {
291 return stream;
292 }
293 if let Some(e) = args.get(1).cloned() {
294 if !with_host(|h| h.is_nullish(&e)) {
295 let _ = emit_event(&stream, "error", vec![e]);
296 }
297 }
298 let _ = emit_event(&stream, "close", vec![]);
299 set_flag(&stream, "@@destroyed", Value::Bool(true));
300 stream
301}
302
303fn add_abort_signal(args: &[Value]) -> Value {
306 args.get(1).cloned().unwrap_or(Value::Undef)
307}
308
309pub fn instance_call(
312 tag: &str,
313 recv: &Value,
314 method: &str,
315 args: Vec<Value>,
316) -> Result<Value, String> {
317 let _ = tag;
318 if method == "emit" {
319 let name = args
320 .first()
321 .map(|v| with_host(|h| h.str_of(v)))
322 .unwrap_or_default();
323 let extra = args.get(1..).map(|s| s.to_vec()).unwrap_or_default();
324 return emit_event(recv, &name, extra);
325 }
326 if matches!(
327 method,
328 "on" | "addListener"
329 | "prependListener"
330 | "once"
331 | "prependOnceListener"
332 | "removeListener"
333 | "off"
334 | "removeAllListeners"
335 | "listenerCount"
336 | "eventNames"
337 ) {
338 return super::events::instance_call(recv, method, args);
339 }
340 match method {
341 "write" => {
342 let chunk = args.first().cloned().unwrap_or(Value::Undef);
343 emit_event(recv, "data", vec![chunk])?;
344 Ok(Value::Bool(true))
345 }
346 "end" => {
347 if let Some(chunk) = args.first().filter(|v| !matches!(v, Value::Undef)) {
348 emit_event(recv, "data", vec![chunk.clone()])?;
349 }
350 emit_event(recv, "finish", vec![])?;
351 emit_event(recv, "end", vec![])?;
352 Ok(recv.clone())
353 }
354 "push" => {
355 let chunk = args.first().cloned().unwrap_or(Value::Undef);
356 if with_host(|h| h.is_nullish(&chunk)) {
357 emit_event(recv, "end", vec![])?;
358 return Ok(Value::Bool(false));
359 }
360 if let Some(q) = queue_of(recv) {
361 with_host(|h| {
362 if let Some(JsObj::Array(items)) = h.get_mut(&q) {
363 items.push(chunk.clone());
364 }
365 });
366 }
367 emit_event(recv, "data", vec![chunk])?;
368 Ok(Value::Bool(true))
369 }
370 "read" => {
371 set_flag(recv, "@@disturbed", Value::Bool(true));
372 if let Some(q) = queue_of(recv) {
373 let next = with_host(|h| match h.get_mut(&q) {
374 Some(JsObj::Array(items)) if !items.is_empty() => Some(items.remove(0)),
375 _ => None,
376 });
377 if let Some(v) = next {
378 return Ok(v);
379 }
380 }
381 Ok(with_host(|h| h.null()))
382 }
383 "pipe" => {
384 set_flag(recv, "@@disturbed", Value::Bool(true));
385 let dest = args.first().cloned().unwrap_or(Value::Undef);
386 if let Some(q) = queue_of(recv) {
387 let items = with_host(|h| match h.get(&q) {
388 Some(JsObj::Array(items)) => items.clone(),
389 _ => Vec::new(),
390 });
391 for chunk in items {
392 crate::host::call_method(&dest, "write", vec![chunk])?;
393 }
394 }
395 Ok(dest)
396 }
397 "destroy" => {
398 if !flag(recv, "@@destroyed") {
399 if let Some(e) = args.first().filter(|v| !matches!(v, Value::Undef)) {
400 let _ = emit_event(recv, "error", vec![e.clone()]);
401 }
402 let _ = emit_event(recv, "close", vec![]);
403 set_flag(recv, "@@destroyed", Value::Bool(true));
404 }
405 Ok(recv.clone())
406 }
407 "resume" => {
408 set_flag(recv, "@@disturbed", Value::Bool(true));
409 Ok(recv.clone())
410 }
411 "setEncoding" | "pause" | "cork" | "uncork" => Ok(recv.clone()),
412 _ => Err(crate::host::type_error(&format!(
413 "stream.{method} is not a function"
414 ))),
415 }
416}
417
418fn queue_of(recv: &Value) -> Option<Value> {
419 with_host(|h| match h.get(recv) {
420 Some(JsObj::Object(p)) => p.get("@@queue").cloned(),
421 _ => None,
422 })
423}