tsrun 0.1.23

A TypeScript interpreter designed for embedding in applications
Documentation
//! tsrun:host built-in module
//!
//! Provides the core order system functions for async host operations.

use crate::error::JsError;
use crate::interpreter::Interpreter;
use crate::value::{CheapClone, ExoticObject, Guarded, JsValue};
use crate::{InternalModule, Order, OrderId, RuntimeValue};

/// Create the tsrun:host module
pub fn create_eval_internal_module() -> InternalModule {
    InternalModule::native("tsrun:host")
        .with_function("order", order_syscall, 1)
        .with_function("__cancelOrder__", cancel_order_syscall, 1)
        .with_function("__getOrderId__", get_order_id_syscall, 0)
        .build()
}

/// Native implementation of request
///
/// Creates a pending order and returns a PendingOrder marker. The VM
/// suspends immediately when this function returns, allowing the host
/// to provide any value (plain objects, Promises, primitives).
///
/// This is a blocking syscall - each call suspends execution until
/// the host fulfills the order.
///
/// Usage:
///   const result = await order({ type: "readFile", path: "/foo" });
///
/// For parallel requests (host returns Promises, resolves them later):
///   const p1 = order({ type: "fetch", url: "/a" });  // suspends, host returns Promise
///   const p2 = order({ type: "fetch", url: "/b" });  // suspends, host returns Promise
///   const [a, b] = await Promise.all([p1, p2]);          // awaits both
fn order_syscall(
    interp: &mut Interpreter,
    _this: JsValue,
    args: &[JsValue],
) -> Result<Guarded, JsError> {
    let payload = args.first().cloned().unwrap_or(JsValue::Undefined);

    // Generate unique order ID
    let id = OrderId(interp.next_order_id);
    interp.next_order_id += 1;

    // Create payload RuntimeValue with guard if it's an object
    let payload_rv = if let JsValue::Object(ref obj) = payload {
        let payload_guard = interp.heap.create_guard();
        payload_guard.guard(obj.cheap_clone());
        RuntimeValue::with_guard(payload, payload_guard)
    } else {
        RuntimeValue::unguarded(payload)
    };

    // Record the pending order
    interp.pending_orders.push(Order {
        id,
        payload: payload_rv,
    });

    // Create PendingOrder marker - VM will suspend immediately when this returns
    let marker_guard = interp.heap.create_guard();
    let marker = marker_guard.alloc();
    marker.borrow_mut().exotic = ExoticObject::PendingOrder { id: id.0 };

    Ok(Guarded::with_guard(JsValue::Object(marker), marker_guard))
}

/// Native implementation of __cancelOrder__
///
/// Cancels a pending order.
///
/// Usage: __cancelOrder__(orderId);
fn cancel_order_syscall(
    interp: &mut Interpreter,
    _this: JsValue,
    args: &[JsValue],
) -> Result<Guarded, JsError> {
    let id = match args.first() {
        Some(JsValue::Number(n)) => OrderId(*n as u64),
        _ => return Err(JsError::type_error("__cancelOrder__ requires order ID")),
    };

    // Mark as cancelled
    interp.cancelled_orders.push(id);

    // Remove from pending
    interp.pending_orders.retain(|o| o.id != id);

    // Remove any pending response (in case host already provided one)
    interp.order_responses.remove(&id);

    Ok(Guarded::unguarded(JsValue::Undefined))
}

/// Native implementation of __getOrderId__
///
/// Returns a new unique order ID without creating an order.
/// Useful for tracking purposes.
///
/// Usage: const id = __getOrderId__();
fn get_order_id_syscall(
    interp: &mut Interpreter,
    _this: JsValue,
    _args: &[JsValue],
) -> Result<Guarded, JsError> {
    let id = interp.next_order_id;
    interp.next_order_id += 1;
    Ok(Guarded::unguarded(JsValue::Number(id as f64)))
}