techne-sdk 0.3.8

TechneSDK's logic written once: version ranking (eventState) and offline record verification over the published check, shipped to TypeScript as WebAssembly; with opt-in atproto plumbing (service auth, DID resolution, signed labels, proven record fetches, PDS password sessions) behind the `crypto` and `net` features.
Documentation
//! The WebAssembly exports: [`crate::call`] over linear memory, no bindings generator. JavaScript
//! copies the function name and the JSON arguments in (`techne_alloc`), calls `techne_call`, and reads
//! the answer (`techne_out_ptr`, for the length `techne_call` returned): `{"ok": …}` or `{"err": "…"}`. The module imports
//! nothing, so the same exports run under any WebAssembly host and under its JavaScript translation.

#![deny(unsafe_op_in_unsafe_fn)]

use std::cell::RefCell;

thread_local! {
    static OUT: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
}

/// Reserve `len` zeroed bytes for an input; `techne_call` takes ownership back and frees them.
#[no_mangle]
pub extern "C" fn techne_alloc(len: usize) -> *mut u8 {
    Box::into_raw(vec![0u8; len].into_boxed_slice()).cast()
}

/// Take back an input `techne_alloc` reserved.
///
/// # Safety
/// `ptr` and `len` must be one `techne_alloc(len)` result, not yet taken back.
unsafe fn take(ptr: *mut u8, len: usize) -> Box<[u8]> {
    // SAFETY: the caller passes exactly what `techne_alloc` boxed, once, so this rebuilds that box
    // with its own length (the allocation's layout) and frees it when dropped.
    unsafe { Box::from_raw(std::ptr::slice_from_raw_parts_mut(ptr, len)) }
}

/// Run `name(args)`, both UTF-8 copied in with `techne_alloc`. Returns the answer's length.
///
/// # Safety
/// Each pointer must come from `techne_alloc` with the same length, and is used once.
#[no_mangle]
pub unsafe extern "C" fn techne_call(name: *mut u8, name_len: usize, args: *mut u8, args_len: usize) -> usize {
    // SAFETY: the host passes back the two allocations it made for this call (`sdk/src/wasm/index.ts`).
    let (name, args) = unsafe { (take(name, name_len), take(args, args_len)) };
    let answer = std::str::from_utf8(&name).map_err(|e| e.to_string()).and_then(|name| {
        let args = serde_json::from_slice(&args).map_err(|e| format!("arguments: {e}"))?;
        crate::call(name, &args)
    });
    let out = match answer {
        Ok(v) => serde_json::json!({ "ok": v }),
        Err(e) => serde_json::json!({ "err": e }),
    };
    OUT.with(|o| {
        let mut o = o.borrow_mut();
        *o = out.to_string().into_bytes();
        o.len()
    })
}

/// Where the last answer is (valid until the next call).
#[no_mangle]
pub extern "C" fn techne_out_ptr() -> *const u8 {
    OUT.with(|o| o.borrow().as_ptr())
}