tela-engine 0.1.0

Runtime engine for Tela — React Native for terminals. QuickJS bridge, native APIs, and ratatui renderer.
Documentation
use rquickjs::Ctx;
use std::path::PathBuf;

const STORAGE_BOOTSTRAP: &str = r#"
globalThis.storage = {
    get: function(key) {
        var val = __tela_storage_get__(String(key));
        if (val === null || val === undefined) return null;
        try { return JSON.parse(val); } catch(e) { return val; }
    },
    set: function(key, value) {
        __tela_storage_set__(String(key), JSON.stringify(value));
    },
    remove: function(key) {
        __tela_storage_remove__(String(key));
    },
};
"#;

pub fn register_storage(ctx: &Ctx<'_>, app_name: &str) -> anyhow::Result<()> {
    let storage_dir = storage_dir_for(app_name);
    std::fs::create_dir_all(&storage_dir).ok();

    let dir = storage_dir.clone();
    let get = move |key: String| -> Option<String> {
        let path = dir.join(sanitize_key(&key));
        std::fs::read_to_string(path).ok()
    };

    let dir = storage_dir.clone();
    let set = move |key: String, value: String| {
        let path = dir.join(sanitize_key(&key));
        std::fs::write(path, value).ok();
    };

    let dir = storage_dir;
    let remove = move |key: String| {
        let path = dir.join(sanitize_key(&key));
        std::fs::remove_file(path).ok();
    };

    ctx.globals().set(
        "__tela_storage_get__",
        rquickjs::Function::new(ctx.clone(), get)?,
    )?;
    ctx.globals().set(
        "__tela_storage_set__",
        rquickjs::Function::new(ctx.clone(), set)?,
    )?;
    ctx.globals().set(
        "__tela_storage_remove__",
        rquickjs::Function::new(ctx.clone(), remove)?,
    )?;

    ctx.eval::<(), _>(STORAGE_BOOTSTRAP)
        .map_err(|e| anyhow::anyhow!("failed to register storage bootstrap: {e}"))?;

    Ok(())
}

fn storage_dir_for(app_name: &str) -> PathBuf {
    let base = dirs_next::data_local_dir().unwrap_or_else(|| PathBuf::from(".tela"));
    base.join("tela").join(app_name)
}

fn sanitize_key(key: &str) -> String {
    key.chars()
        .map(|c| {
            if c.is_alphanumeric() || c == '_' || c == '-' {
                c
            } else {
                '_'
            }
        })
        .collect()
}