chore: apply cargo fmt across workspace and update docs

- Run cargo fmt on all Rust crates for consistent formatting
- Update CLAUDE.md with WASM plugin commands and dev.ps1 instructions
- Update wiki: add WASM plugin architecture, rewrite dev environment docs
- Minor frontend cleanup (unused imports)
This commit is contained in:
iven
2026-04-15 00:49:20 +08:00
parent e16c1a85d7
commit 9568dd7875
113 changed files with 4355 additions and 937 deletions

View File

@@ -0,0 +1,208 @@
//! WASM 插件原型验证 — Host 端运行时
//!
//! 验证目标:
//! - V1: WIT 接口定义 + bindgen! 宏编译通过
//! - V2: Host 调用插件导出函数init / handle_event
//! - V3: 插件调用 Host 导入函数db_insert / log_write
//! - V4: async 支持Host async 函数正确桥接)
//! - V5: Fuel + Epoch 资源限制
//! - V6: 从二进制动态加载
use anyhow::Result;
use wasmtime::component::{Component, HasSelf, Linker, bindgen};
use wasmtime::{Config, Engine, Store, StoreLimits, StoreLimitsBuilder};
/// Host 端状态,绑定到每个 Store 实例
pub struct HostState {
/// Store 级资源限制
pub(crate) limits: StoreLimits,
/// 模拟数据库操作记录
pub db_ops: Vec<DbOperation>,
/// 日志记录
pub logs: Vec<(String, String)>,
/// 发布的事件
pub events: Vec<(String, Vec<u8>)>,
/// 配置存储(模拟)
pub config_map: std::collections::HashMap<String, Vec<u8>>,
}
/// 数据库操作记录
#[derive(Debug, Clone)]
pub struct DbOperation {
pub op_type: String,
pub entity: String,
pub data: Option<Vec<u8>>,
pub id: Option<String>,
pub version: Option<i64>,
}
impl Default for HostState {
fn default() -> Self {
Self::new()
}
}
impl HostState {
pub fn new() -> Self {
Self {
limits: StoreLimitsBuilder::new().build(),
db_ops: Vec::new(),
logs: Vec::new(),
events: Vec::new(),
config_map: std::collections::HashMap::new(),
}
}
}
// bindgen! 生成类型化绑定(包含 Host trait 和 add_to_linker
bindgen!({
path: "./wit/plugin.wit",
world: "plugin-world",
});
// 实现 bindgen 生成的 Host trait — 插件调用 Host API 的入口
impl erp::plugin::host_api::Host for HostState {
fn db_insert(&mut self, entity: String, data: Vec<u8>) -> Result<Vec<u8>, String> {
let id = format!("id-{}", self.db_ops.len() + 1);
let record = serde_json::json!({
"id": id,
"tenant_id": "tenant-default",
"entity": entity,
"data": serde_json::from_slice::<serde_json::Value>(&data).unwrap_or(serde_json::Value::Null),
});
let result = serde_json::to_vec(&record).map_err(|e| e.to_string())?;
self.db_ops.push(DbOperation {
op_type: "insert".into(),
entity: entity.clone(),
data: Some(data),
id: Some(id),
version: None,
});
Ok(result)
}
fn db_query(
&mut self,
entity: String,
_filter: Vec<u8>,
_pagination: Vec<u8>,
) -> Result<Vec<u8>, String> {
let results: Vec<serde_json::Value> = self
.db_ops
.iter()
.filter(|op| op.entity == entity && op.op_type == "insert")
.map(|op| {
serde_json::json!({
"id": op.id,
"entity": op.entity,
})
})
.collect();
serde_json::to_vec(&results).map_err(|e| e.to_string())
}
fn db_update(
&mut self,
entity: String,
id: String,
data: Vec<u8>,
version: i64,
) -> Result<Vec<u8>, String> {
let record = serde_json::json!({
"id": id,
"tenant_id": "tenant-default",
"entity": entity,
"version": version + 1,
"data": serde_json::from_slice::<serde_json::Value>(&data).unwrap_or(serde_json::Value::Null),
});
let result = serde_json::to_vec(&record).map_err(|e| e.to_string())?;
self.db_ops.push(DbOperation {
op_type: "update".into(),
entity,
data: Some(data),
id: Some(id),
version: Some(version),
});
Ok(result)
}
fn db_delete(&mut self, entity: String, id: String) -> Result<(), String> {
self.db_ops.push(DbOperation {
op_type: "delete".into(),
entity,
data: None,
id: Some(id),
version: None,
});
Ok(())
}
fn event_publish(&mut self, event_type: String, payload: Vec<u8>) -> Result<(), String> {
self.events.push((event_type, payload));
Ok(())
}
fn config_get(&mut self, key: String) -> Result<Vec<u8>, String> {
self.config_map
.get(&key)
.cloned()
.ok_or_else(|| format!("配置项 '{}' 不存在", key))
}
fn log_write(&mut self, level: String, message: String) {
self.logs.push((level, message));
}
fn current_user(&mut self) -> Result<Vec<u8>, String> {
let user = serde_json::json!({
"id": "user-default",
"username": "admin",
"tenant_id": "tenant-default",
});
serde_json::to_vec(&user).map_err(|e| e.to_string())
}
fn check_permission(&mut self, _permission: String) -> Result<bool, String> {
Ok(true)
}
}
/// 创建 Wasmtime Engine启用 Fuel 限制)
pub fn create_engine() -> Result<Engine> {
let mut config = Config::new();
config.wasm_component_model(true);
config.consume_fuel(true);
Ok(Engine::new(&config)?)
}
/// 创建带 Fuel 限制的 Store
pub fn create_store(engine: &Engine, fuel: u64) -> Result<Store<HostState>> {
let state = HostState::new();
let mut store = Store::new(engine, state);
store.set_fuel(fuel)?;
store.limiter(|state| &mut state.limits);
Ok(store)
}
/// 从 WASM 二进制加载并实例化插件
pub async fn load_plugin(
engine: &Engine,
wasm_bytes: &[u8],
fuel: u64,
) -> Result<(Store<HostState>, PluginWorld)> {
let mut store = create_store(engine, fuel)?;
let component = Component::from_binary(engine, wasm_bytes)?;
let mut linker = Linker::new(engine);
// 注册 Host API 到 Linker使插件可以调用 host 函数
// HasSelf<HostState> 表示 Data<'a> = &'a mut HostState
PluginWorld::add_to_linker::<_, HasSelf<HostState>>(&mut linker, |state| state)?;
let instance = PluginWorld::instantiate_async(&mut store, &component, &linker).await?;
Ok((store, instance))
}

View File

@@ -0,0 +1,10 @@
use erp_plugin_prototype::*;
fn _discover_api(instance: PluginWorld, mut store: wasmtime::Store<HostState>) {
let api = instance.erp_plugin_plugin_api();
let _ = api.call_init(&mut store);
let _ = api.call_on_tenant_created(&mut store, "test-tenant");
let _ = api.call_handle_event(&mut store, "test.event", &[1u8]);
}
fn main() {}