ts-function 0.2.0

A proc-macro that generates TypeScript type aliases and wasm-bindgen ABI trait impls for Rust callback wrapper structs
Documentation
use ts_function::{ts, ts_function};
use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*;

// ==========================================
// Example 1: Primary API (Type Alias)
// Taking two Uint8Array and XORing them
// ==========================================

#[ts_function]
pub type XorCallback = fn(a: js_sys::Uint8Array, b: js_sys::Uint8Array);

#[wasm_bindgen(module = "/tests/basic.js")]
extern "C" {
    fn get_xor_callback() -> js_sys::Function;
    fn get_xor_result() -> js_sys::Uint8Array;
}

#[wasm_bindgen_test]
fn test_example_1_primary_api() {
    // 1. Get the JS callback that performs the XOR operation
    let js_func = get_xor_callback();

    // 2. Wrap it into our typed wrapper (ts_function generated the From impl)
    let cb = XorCallback::from(js_func);

    // 3. Create two Uint8Array inputs
    let a_bytes: &[u8] = &[0b10101010, 0b11110000, 0b00001111];
    let b_bytes: &[u8] = &[0b01010101, 0b11001100, 0b00110011];

    let a = js_sys::Uint8Array::from(a_bytes);
    let b = js_sys::Uint8Array::from(b_bytes);

    // 4. Call the JS function securely using our strongly typed Rust signature
    cb.call(a, b).unwrap();

    // 5. Verify the JavaScript environment did the correct XOR operation
    let res = get_xor_result();
    let res_vec = res.to_vec();

    assert_eq!(res_vec, vec![0b11111111, 0b00111100, 0b00111100]);
}

// ==========================================
// Example 2: Escape Hatch API
// Custom error handling when JS throws
// ==========================================

pub struct SafeCallback(pub ::js_sys::Function);

#[ts_function]
impl SafeCallback {
    pub fn call(&self, val: f64) {
        // Execute the function but catch the error thrown by JS
        let result = self.0.call1(
            &::wasm_bindgen::JsValue::NULL,
            &::wasm_bindgen::JsValue::from_f64(val),
        );

        if let Err(e) = result {
            // Downcast the JsValue error to a JS Error object and record its message
            if let Ok(err_obj) = e.dyn_into::<js_sys::Error>() {
                set_handled_error(err_obj.message().into());
            }
        }
    }
}

#[wasm_bindgen(module = "/tests/basic.js")]
extern "C" {
    fn get_throwing_callback() -> js_sys::Function;
    fn get_handled_error() -> String;
    fn set_handled_error(err: String);
}

#[wasm_bindgen_test]
fn test_example_2_escape_hatch() {
    // 1. Reset state
    set_handled_error("".to_string());

    // 2. Get the JS callback that is guaranteed to throw an error
    let js_func = get_throwing_callback();
    let cb = SafeCallback::from(js_func);

    // 3. This call handles the error internally instead of panicking
    cb.call(42.0);

    // 4. Verify the error message was caught and exported properly
    assert_eq!(get_handled_error(), "JS error with value 42");
}

// ==========================================
// Example 3: ts_macro Callback Struct Pattern
// Passing a struct of callbacks from JS
// ==========================================

#[ts_function]
pub type SimpleCb = fn(msg: String);

// ts_macro creates an interface `IMyCallbacks` which `parse()`s into `MyCallbacks`
#[ts]
struct MyCallbacks {
    on_event: SimpleCb,
}

#[wasm_bindgen(module = "/tests/basic.js")]
extern "C" {
    fn get_simple_cb() -> js_sys::Function;
    fn get_state_msg() -> String;
}

#[wasm_bindgen_test]
fn test_example_3_ts_macro_struct() {
    // 1. Simulate the JS environment creating an object with callback functions
    let raw_js_obj = js_sys::Object::new();
    js_sys::Reflect::set(&raw_js_obj, &"onEvent".into(), &get_simple_cb()).unwrap();

    // 2. Convert to the ts_macro generated JS interface type `IMyCallbacks`
    let icallbacks = IMyCallbacks::from(JsValue::from(raw_js_obj));

    // 3. Use `.parse()` to convert into the Rust native struct.
    //    This is where the ABI traits generated by `ts_function` are used.
    let callbacks: MyCallbacks = icallbacks.parse();

    // 4. Fire the callback!
    callbacks
        .on_event
        .call("Hello from ts_macro!".to_string())
        .unwrap();

    // 5. Verify the JS function ran
    assert_eq!(get_state_msg(), "Hello from ts_macro!");
}